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.4
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 
24 Item {
25  id: root
26 
27  property string background
28 
29  property var screensProxy: Screens.createProxy();
30 
31  property QtObject activeWorkspace: null
32 
33  signal closeSpread();
34 
35  Row {
36  id: row
37  anchors.bottom: parent.bottom
38  anchors.horizontalCenter: parent.horizontalCenter
39  Behavior on anchors.horizontalCenterOffset { NumberAnimation { duration: LomiriAnimation.SlowDuration } }
40  spacing: units.gu(1)
41 
42  property var selectedIndex: undefined
43 
44  Repeater {
45  model: screensProxy
46 
47  delegate: Item {
48  height: root.height - units.gu(6)
49  width: workspaces.width
50 
51  Item {
52  id: header
53  anchors { left: parent.left; top: parent.top; right: parent.right }
54  height: units.gu(7)
55  z: 1
56 
57  property bool isCurrent: {
58  // another screen is selected.
59  if (row.selectedIndex != undefined && row.selectedIndex != index) return false;
60 
61  // this screen is active.
62  if (WMScreen.active && WMScreen.isSameAs(model.screen) && WMScreen.currentWorkspace.isSameAs(activeWorkspace)) return true;
63  if (model.screen.workspaces.indexOf(activeWorkspace) >= 0) return true;
64 
65  // not active.
66  return false;
67  }
68 
69  property bool isSelected: screenMA.containsMouse
70  onIsSelectedChanged: {
71  if (isSelected) {
72  row.selectedIndex = Qt.binding(function() { return index; });
73  } else if (row.selectedIndex === index) {
74  row.selectedIndex = undefined;
75  }
76  }
77 
78  LomiriShape {
79  anchors.fill: parent
80  backgroundColor: "white"
81  opacity: header.isCurrent || header.isSelected ? 1.0 : 0.5
82  }
83 
84  DropArea {
85  anchors.fill: parent
86  keys: ["workspace"]
87 
88  onEntered: {
89  workspaces.workspaceModel.insert(workspaces.workspaceModel.count, {text: drag.source.text})
90  drag.source.inDropArea = true;
91  }
92 
93  onExited: {
94  workspaces.workspaceModel.remove(workspaces.workspaceModel.count - 1, 1)
95  drag.source.inDropArea = false;
96  }
97 
98  onDropped: {
99  drag.source.inDropArea = false;
100  }
101  }
102 
103  Column {
104  anchors.fill: parent
105  anchors.margins: units.gu(1)
106 
107  Label {
108  text: model.screen.name
109  color: header.isCurrent || header.isSelected ? "black" : "white"
110  }
111 
112  Label {
113  text: model.screen.outputTypeName
114  color: header.isCurrent || header.isSelected ? "black" : "white"
115  fontSize: "x-small"
116  }
117 
118  Label {
119  text: screen.availableModes[screen.currentModeIndex].size.width + "x" + screen.availableModes[screen.currentModeIndex].size.height
120  color: header.isCurrent || header.isSelected ? "black" : "white"
121  fontSize: "x-small"
122  }
123  }
124 
125  Icon {
126  anchors {
127  top: parent.top
128  right: parent.right
129  margins: units.gu(1)
130  }
131  width: units.gu(3)
132  height: width
133  source: "image://theme/select"
134  color: header.isCurrent || header.isSelected ? "black" : "white"
135  visible: model.screen.active
136  }
137 
138  MouseArea {
139  id: screenMA
140  hoverEnabled: true
141  anchors.fill: parent
142 
143  onClicked: {
144  var obj = screensMenuComponent.createObject(header)
145  obj.open(mouseX, mouseY)
146  }
147  }
148 
149  Component {
150  id: screensMenuComponent
151  LomiriShape {
152  id: screensMenu
153  width: units.gu(20)
154  height: contentColumn.childrenRect.height
155  backgroundColor: "white"
156 
157  function open(mouseX, mouseY) {
158  x = Math.max(0, Math.min(mouseX - width / 2, parent.width - width))
159  y = mouseY + units.gu(1)
160  }
161 
162  InverseMouseArea {
163  anchors.fill: parent
164  onClicked: {
165  screensMenu.destroy()
166  }
167  }
168 
169  Column {
170  id: contentColumn
171  width: parent.width
172  ListItem {
173  height: layout.height
174  highlightColor: "transparent"
175  ListItemLayout {
176  id: layout
177  title.text: qsTr("Add workspace")
178  title.color: "black"
179  }
180  onClicked: {
181  screen.workspaces.addWorkspace();
182  Screens.sync(root.screensProxy);
183  screensMenu.destroy();
184  }
185  }
186  }
187  }
188  }
189  }
190 
191  Workspaces {
192  id: workspaces
193  height: parent.height - header.height - units.gu(2)
194  width: {
195  var width = 0;
196  if (screensProxy.count == 1) {
197  width = Math.min(implicitWidth, root.width - units.gu(8));
198  } else {
199  width = Math.min(implicitWidth, model.screen.active ? root.width - units.gu(48) : units.gu(40))
200  }
201  return Math.max(workspaces.minimumWidth, width);
202  }
203 
204  Behavior on width { LomiriNumberAnimation {} }
205  anchors.bottom: parent.bottom
206  anchors.bottomMargin: units.gu(1)
207  anchors.horizontalCenter: parent.horizontalCenter
208  screen: model.screen
209  background: root.background
210 
211  workspaceModel: model.screen.workspaces
212  activeWorkspace: root.activeWorkspace
213  readOnly: false
214 
215  onCommitScreenSetup: Screens.sync(root.screensProxy)
216  onCloseSpread: root.closeSpread();
217 
218  onClicked: {
219  root.activeWorkspace = workspace;
220  }
221  }
222  }
223  }
224  }
225 
226  Rectangle {
227  anchors { left: parent.left; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
228  width: units.gu(5)
229  color: "#33000000"
230  visible: (row.width - root.width + units.gu(10)) / 2 - row.anchors.horizontalCenterOffset > units.gu(5)
231  MouseArea {
232  id: leftScrollArea
233  anchors.fill: parent
234  hoverEnabled: true
235  onPressed: mouse.accepted = false;
236  }
237  DropArea {
238  id: leftFakeDropArea
239  anchors.fill: parent
240  keys: ["application", "workspace"]
241  }
242  }
243  Rectangle {
244  anchors { right: parent.right; top: parent.top; bottom: parent.bottom; topMargin: units.gu(6); bottomMargin: units.gu(1) }
245  width: units.gu(5)
246  color: "#33000000"
247  visible: (row.width - root.width + units.gu(10)) / 2 + row.anchors.horizontalCenterOffset > units.gu(5)
248  MouseArea {
249  id: rightScrollArea
250  anchors.fill: parent
251  hoverEnabled: true
252  onPressed: mouse.accepted = false;
253  }
254  DropArea {
255  id: rightFakeDropArea
256  anchors.fill: parent
257  keys: ["application", "workspace"]
258  }
259  }
260  Timer {
261  repeat: true
262  running: leftScrollArea.containsMouse || rightScrollArea.containsMouse || leftFakeDropArea.containsDrag || rightFakeDropArea.containsDrag
263  interval: LomiriAnimation.SlowDuration
264  triggeredOnStart: true
265  onTriggered: {
266  var newOffset = row.anchors.horizontalCenterOffset;
267  var maxOffset = Math.max((row.width - root.width + units.gu(10)) / 2, 0);
268  if (leftScrollArea.containsMouse || leftFakeDropArea.containsDrag) {
269  newOffset += units.gu(20)
270  } else {
271  newOffset -= units.gu(20)
272  }
273  newOffset = Math.max(-maxOffset, Math.min(maxOffset, newOffset));
274  row.anchors.horizontalCenterOffset = newOffset;
275  }
276  }
277 }