Lomiri
ScreensAndWorkspaces.qml
1 /*
2  * Copyright (C) 2017 Canonical Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.12
18 import Lomiri.Components 1.3
19 import Lomiri.Components.Popups 1.3
20 import WindowManager 1.0
21 import QtMir.Application 0.1
22 import ".."
23 import "../.."
24 
25 Item {
26  id: root
27 
28  property string background
29 
30  property var screensProxy: Screens.createProxy();
31 
32  property QtObject activeWorkspace: null
33 
34  property string mode : "staged"
35 
36  signal closeSpread();
37 
38  DeviceConfiguration {
39  id: deviceConfiguration
40  }
41 
42  Row {
43  id: row
44  anchors.bottom: parent.bottom
45  anchors.horizontalCenter: parent.horizontalCenter
46  Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: LomiriAnimation.SlowDuration } }
47  spacing: units.gu(1)
48 
49  property var selectedIndex: undefined
50 
51  Repeater {
52  model: screensProxy
53 
54  delegate: Item {
55  height: root.height - units.gu(6)
56  width: workspaces.width
57  visible: (deviceConfiguration.category == "phone" && index !== 0) || deviceConfiguration.category != "phone"
58 
59  Item {
60  id: header
61  anchors { left: parent.left; top: parent.top; right: parent.right }
62  height: units.gu(7)
63  z: 1
64 
65  property bool isCurrent: {
66  // another screen is selected.
67  if (row.selectedIndex != undefined && row.selectedIndex != index) return false;
68 
69  // this screen is active.
70  if (WMScreen.active && WMScreen.isSameAs(model.screen) && WMScreen.currentWorkspace.isSameAs(activeWorkspace)) return true;
71  if (model.screen.workspaces.indexOf(activeWorkspace) >= 0) return true;
72 
73  // not active.
74  return false;
75  }
76 
77  property bool isSelected: screenMA.containsMouse
78  onIsSelectedChanged: {
79  if (isSelected) {
80  row.selectedIndex = Qt.binding(function() { return index; });
81  } else if (row.selectedIndex === index) {
82  row.selectedIndex = undefined;
83  }
84  }
85 
86  LomiriShape {
87  anchors.fill: parent
88  backgroundColor: "white"
89  opacity: header.isCurrent || header.isSelected ? 1.0 : 0.5
90  }
91 
92  DropArea {
93  anchors.fill: parent
94  keys: ["workspace"]
95 
96  onEntered: {
97  workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
98  drag.source.inDropArea = true;
99  }
100 
101  onExited: {
102  workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
103  drag.source.inDropArea = false;
104  }
105 
106  onDropped: {
107  drag.source.inDropArea = false;
108  }
109  }
110 
111  Column {
112  anchors.fill: parent
113  anchors.margins: units.gu(1)
114 
115  Label {
116  text: model.screen.name
117  color: header.isCurrent || header.isSelected ? "black" : "white"
118  }
119 
120  Label {
121  text: model.screen.outputTypeName
122  color: header.isCurrent || header.isSelected ? "black" : "white"
123  fontSize: "x-small"
124  }
125 
126  Label {
127  text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
128  color: header.isCurrent || header.isSelected ? "black" : "white"
129  fontSize: "x-small"
130  }
131  }
132 
133  Icon {
134  anchors {
135  top: parent.top
136  right: parent.right
137  margins: units.gu(1)
138  }
139  width: units.gu(3)
140  height: width
141  source: "image://theme/select"
142  color: header.isCurrent || header.isSelected ? "black" : "white"
143  visible: model.screen.active
144  }
145 
146  MouseArea {
147  id: screenMA
148  hoverEnabled: true
149  anchors.fill: parent
150 
151  onClicked: {
152  var obj = screensMenuComponent.createObject(header)
153  obj.open(mouseX, mouseY)
154  }
155  }
156 
157  Component {
158  id: screensMenuComponent
159  LomiriShape {
160  id: screensMenu
161  width: units.gu(20)
162  height: contentColumn.childrenRect.height
163  backgroundColor: "white"
164 
165  function open(mouseX, mouseY) {
166  x = Math.max(0, Math.min(mouseX - width / 2, parent.width - width))
167  y = mouseY + units.gu(1)
168  }
169 
170  InverseMouseArea {
171  anchors.fill: parent
172  onClicked: {
173  screensMenu.destroy()
174  }
175  }
176 
177  Column {
178  id: contentColumn
179  width: parent.width
180  ListItem {
181  height: layout.height
182  highlightColor: "transparent"
183  ListItemLayout {
184  id: layout
185  title.text: qsTr("Add workspace")
186  title.color: "black"
187  }
188  onClicked: {
189  screen.workspaces.addWorkspace();
190  Screens.sync(root.screensProxy);
191  screensMenu.destroy();
192  }
193  }
194  }
195  }
196  }
197  }
198 
199  Workspaces {
200  id: workspaces
201  height: parent.height - header.height - units.gu(2)
202  width: {
203  var width = 0;
204  if (screensProxy.count == 1) {
205  width = Math.min(implicitWidth, root.width - units.gu(8));
206  } else {
207  width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
208  }
209  return Math.max(workspaces.minimumWidth, width);
210  }
211 
212  Behavior on width { LomiriNumberAnimation {} }
213  anchors.bottom: parent.bottom
214  anchors.bottomMargin: units.gu(1)
215  anchors.horizontalCenter: parent.horizontalCenter
216  screen: model.screen
217  background: root.background
218 
219  workspaceModel: model.screen.workspaces
220  activeWorkspace: root.activeWorkspace
221  readOnly: false
222 
223  onCommitScreenSetup: Screens.sync(root.screensProxy)
224  onCloseSpread: root.closeSpread();
225 
226  onClicked: {
227  root.activeWorkspace = workspace;
228  }
229  }
230  }
231  }
232  }
233 
234  Rectangle {
235  anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
236  width: units.gu(5)
237  color: "#33000000"
238  visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
239  MouseArea {
240  id: leftScrollArea
241  anchors.fill: parent
242  hoverEnabled: true
243  onPressed: mouse.accepted = false;
244  }
245  DropArea {
246  id: leftFakeDropArea
247  anchors.fill: parent
248  keys: ["application", "workspace"]
249  }
250  }
251  Rectangle {
252  anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
253  width: units.gu(5)
254  color: "#33000000"
255  visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
256  MouseArea {
257  id: rightScrollArea
258  anchors.fill: parent
259  hoverEnabled: true
260  onPressed: mouse.accepted = false;
261  }
262  DropArea {
263  id: rightFakeDropArea
264  anchors.fill: parent
265  keys: ["application", "workspace"]
266  }
267  }
268  Timer {
269  repeat: true
270  running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
271  interval: LomiriAnimation.SlowDuration
272  triggeredOnStart: true
273  onTriggered: {
274  var newOffset = row.anchors.horizontalCenterOffset;
275  var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
276  if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
277  newOffset += units.gu(20)
278  } else {
279  newOffset -= units.gu(20)
280  }
281  newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
282  row.anchors.horizontalCenterOffset = newOffset;
283  }
284  }
285 }