Lomiri
Loading...
Searching...
No Matches
Workspaces.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
17import QtQuick 2.15
18import Lomiri.Components 1.3
19import GSettings 1.0
20import WindowManager 1.0
21import "MathUtils.js" as MathUtils
22import "../../Components"
23
24Item {
25 id: root
26 implicitWidth: listView.contentWidth
27 readonly property int minimumWidth: {
28 var count = Math.min(3, listView.count);
29 return listView.itemWidth * count + listView.spacing * (count - 1)
30 }
31
32 property QtObject screen: null
33 property alias workspaceModel: listView.model
34 property var background // TODO: should be stored in the workspace data
35 property int selectedIndex: -1
36 property bool readOnly: true
37 property var activeWorkspace: null
38 property Item availableDesktopArea
39
40 signal commitScreenSetup();
41 signal closeSpread();
42 signal clicked(var workspace);
43
44 DropArea {
45 anchors.fill: root
46
47 keys: ['workspace']
48
49 onEntered: {
50 var index = listView.getDropIndex(drag);
51 drag.source.workspace.assign(workspaceModel, index)
52 drag.source.inDropArea = true;
53 }
54
55 onPositionChanged: {
56 var index = listView.getDropIndex(drag);
57 if (listView.dropItemIndex == index) return;
58 listView.model.move(listView.dropItemIndex, index, 1);
59 listView.dropItemIndex = index;
60 }
61
62 onExited: {
63 drag.source.workspace.unassign()
64 listView.dropItemIndex = -1;
65 listView.hoveredWorkspaceIndex = -1;
66 drag.source.inDropArea = false;
67 }
68
69 onDropped: {
70 drop.accept(Qt.MoveAction);
71 listView.dropItemIndex = -1;
72 drag.source.inDropArea = false;
73 }
74 }
75 DropArea {
76 anchors.fill: parent
77 keys: ["application"]
78
79 onPositionChanged: {
80 listView.progressiveScroll(drag.x)
81 listView.updateDropProperties(drag)
82 }
83 onExited: {
84 listView.hoveredWorkspaceIndex = -1
85 }
86 onDropped: {
87 var surface = drag.source.surface;
88 drag.source.surface = null;
89 var workspace = listView.model.get(listView.hoveredWorkspaceIndex);
90 WorkspaceManager.moveSurfaceToWorkspace(surface, workspace);
91 drop.accept(Qt.MoveAction)
92 if (listView.hoveredHalf == "right") {
93 root.closeSpread();
94 workspace.activate();
95 }
96 surface.activate();
97 listView.hoveredWorkspaceIndex = -1
98 }
99 }
100
101 onSelectedIndexChanged: {
102 listView.positionViewAtIndex(selectedIndex, ListView.Center);
103 }
104
105 Item {
106 // We need to clip the listview as it has left/right margins and it would
107 // overlap with items next to it and eat mouse input. However, we can't
108 // just clip at the actual bounds as the delegates have the close button
109 // on hover which reaches a bit outside, so lets some margins for the clipping
110 anchors.fill: parent
111 anchors.margins: -units.gu(2)
112 clip: true
113
114
115 ListView {
116 id: listView
117 anchors {
118 fill: parent
119 topMargin: -parent.anchors.margins
120 bottomMargin: -parent.anchors.margins
121 leftMargin: -itemWidth - parent.anchors.margins
122 rightMargin: -itemWidth - parent.anchors.margins
123 }
124 boundsBehavior: Flickable.StopAtBounds
125
126 Behavior on contentX {
127 SmoothedAnimation { duration: 200 }
128 }
129
130 property var clickedWorkspace: null
131
132 orientation: ListView.Horizontal
133 spacing: units.gu(2)
134 leftMargin: itemWidth
135 rightMargin: itemWidth
136 contentX: anchors.leftMargin
137
138 // FIXME: Screen orientation changed event does not trigger properly
139 // so we rely on width/height getting changed when rotating hence updating the value as needed
140 readonly property bool screenIsLandscape: screen.orientation == Qt.LandscapeOrientation
141 || screen.orientation == Qt.InvertedLandscapeOrientation ? width > 0 || height > 0
142 : width < 0 || height < 0
143
144 // Get the screen size based on screen's current orientation
145 readonly property var screenSize: screen.availableModes[screen.currentModeIndex].size
146 readonly property real screenWidth: screenIsLandscape ? screenSize.width >= screenSize.height ? screenSize.width : screenSize.height
147 : screenSize.width >= screenSize.height ? screenSize.height : screenSize.width
148 readonly property real screenHeight: screenIsLandscape ? screenSize.width >= screenSize.height ? screenSize.height : screenSize.width
149 : screenSize.width >= screenSize.height ? screenSize.width : screenSize.height
150
151 readonly property real screenSpaceHeight: root.availableDesktopArea.height
152 readonly property real screenSpaceWidth: root.availableDesktopArea.width
153 readonly property real launcherWidth: screenWidth - screenSpaceWidth
154 property real itemWidth: height * screenSpaceWidth / screenSpaceHeight
155 property int foldingAreaWidth: itemWidth / 2
156 property int maxAngle: 40
157
158 property real realContentX: contentX - originX + leftMargin
159 property int dropItemIndex: -1
160 property int hoveredWorkspaceIndex: -1
161 property string hoveredHalf: "" // left or right
162
163 function getDropIndex(drag) {
164 var coords = mapToItem(listView.contentItem, drag.x, drag.y)
165 var index = Math.floor((drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing));
166 if (index < 0) index = 0;
167 var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
168 if (index > upperLimit) index = upperLimit;
169 return index;
170 }
171
172 function updateDropProperties(drag) {
173 var coords = mapToItem(listView.contentItem, drag.x, drag.y)
174 var index = Math.floor(drag.x + listView.realContentX) / (listView.itemWidth + listView.spacing);
175 if (index < 0) {
176 listView.hoveredWorkspaceIndex = -1;
177 listView.hoveredHalf = "";
178 return;
179 }
180
181 var upperLimit = dropItemIndex == -1 ? listView.count : listView.count - 1
182 if (index > upperLimit) index = upperLimit;
183 listView.hoveredWorkspaceIndex = index;
184 var pixelsInTile = (drag.x + listView.realContentX) % (listView.itemWidth + listView.spacing);
185 listView.hoveredHalf = (pixelsInTile / listView.itemWidth) < .5 ? "left" : "right";
186 }
187
188 function progressiveScroll(mouseX) {
189 var progress = Math.max(0, Math.min(1, (mouseX - listView.itemWidth) / (width - listView.leftMargin * 2 - listView.itemWidth * 2)))
190 listView.contentX = listView.originX + (listView.contentWidth - listView.width + listView.leftMargin + listView.rightMargin) * progress - listView.leftMargin
191 }
192
193 displaced: Transition { LomiriNumberAnimation { properties: "x" } }
194
195 delegate: Item {
196 id: workspaceDelegate
197 objectName: "delegate" + index
198 height: parent.height
199 width: listView.itemWidth
200 Behavior on width { LomiriNumberAnimation {} }
201 visible: listView.dropItemIndex !== index
202
203 property int itemX: -listView.realContentX + index * (listView.itemWidth + listView.spacing)
204 property int distanceFromLeft: itemX //- listView.leftMargin
205 property int distanceFromRight: listView.width - listView.leftMargin - listView.rightMargin - itemX - listView.itemWidth
206
207 property int itemAngle: {
208 if (index == 0) {
209 if (distanceFromLeft < 0) {
210 var progress = (distanceFromLeft + listView.foldingAreaWidth) / listView.foldingAreaWidth
211 return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
212 }
213 return 0
214 }
215 if (index == listView.count - 1) {
216 if (distanceFromRight < 0) {
217 var progress = (distanceFromRight + listView.foldingAreaWidth) / listView.foldingAreaWidth
218 return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
219 }
220 return 0
221 }
222
223 if (distanceFromLeft < listView.foldingAreaWidth) {
224 // itemX : 10gu = p : 100
225 var progress = distanceFromLeft / listView.foldingAreaWidth
226 return MathUtils.linearAnimation(1, -1, 0, listView.maxAngle, Math.max(-1, Math.min(1, progress)));
227 }
228 if (distanceFromRight < listView.foldingAreaWidth) {
229 var progress = distanceFromRight / listView.foldingAreaWidth
230 return MathUtils.linearAnimation(1, -1, 0, -listView.maxAngle, Math.max(-1, Math.min(1, progress)));
231 }
232 return 0
233 }
234
235 property int itemOffset: {
236 if (index == 0) {
237 if (distanceFromLeft < 0) {
238 return -distanceFromLeft
239 }
240 return 0
241 }
242 if (index == listView.count - 1) {
243 if (distanceFromRight < 0) {
244 return distanceFromRight
245 }
246 return 0
247 }
248
249 if (itemX < -listView.foldingAreaWidth) {
250 return -itemX
251 }
252 if (distanceFromLeft < listView.foldingAreaWidth) {
253 return (listView.foldingAreaWidth - distanceFromLeft) / 2
254 }
255
256 if (distanceFromRight < -listView.foldingAreaWidth) {
257 return distanceFromRight
258 }
259
260 if (distanceFromRight < listView.foldingAreaWidth) {
261 return -(listView.foldingAreaWidth - distanceFromRight) / 2
262 }
263
264 return 0
265 }
266
267 z: itemOffset < 0 ? itemOffset : -itemOffset
268 transform: [
269 Rotation {
270 angle: itemAngle
271 axis { x: 0; y: 1; z: 0 }
272 origin { x: itemAngle < 0 ? listView.itemWidth : 0; y: height / 2 }
273 },
274 Translate {
275 x: itemOffset
276 }
277 ]
278
279 WorkspacePreview {
280 id: workspacePreview
281 height: listView.height
282 width: listView.itemWidth - settings.launcherWidth
283 anchors.horizontalCenter: parent.horizontalCenter
284 screen: root.screen
285 background: root.background
286 screenHeight: listView.screenSpaceHeight
287 launcherWidth: listView.launcherWidth
288 containsDragLeft: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "left"
289 containsDragRight: listView.hoveredWorkspaceIndex == index && listView.hoveredHalf == "right"
290 isActive: workspace.isSameAs(root.activeWorkspace)
291 isSelected: index === root.selectedIndex
292 workspace: model.workspace
293 }
294 MouseArea {
295 anchors.fill: parent
296 onClicked: {
297 root.clicked(model.workspace)
298 }
299 onDoubleClicked: {
300 model.workspace.activate();
301 root.closeSpread();
302 }
303 }
304
305 MouseArea {
306 id: closeMouseArea
307 objectName: "closeMouseArea"
308 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 }
309 hoverEnabled: true
310 height: units.gu(4)
311 width: height
312 visible: !root.readOnly && listView.count > 1
313
314 onClicked: {
315 model.workspace.unassign();
316 root.commitScreenSetup();
317 }
318 Image {
319 id: closeImage
320 source: "../graphics/window-close.svg"
321 anchors.fill: closeMouseArea
322 anchors.margins: units.gu(1)
323 sourceSize.width: width
324 sourceSize.height: height
325 readonly property var mousePos: hoverMouseArea.mapToItem(workspaceDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
326 readonly property bool shown: (hoverMouseArea.containsMouse || parent.containsMouse)
327 && mousePos.y < workspaceDelegate.width / 4
328 && mousePos.y > -units.gu(2)
329 && mousePos.x > -units.gu(2)
330 && mousePos.x < workspaceDelegate.height / 4
331 opacity: shown ? 1 : 0
332 visible: opacity > 0
333 Behavior on opacity { LomiriNumberAnimation { duration: LomiriAnimation.SnapDuration } }
334
335 }
336 }
337 }
338
339 MouseArea {
340 id: hoverMouseArea
341 anchors.fill: parent
342 hoverEnabled: true
343 propagateComposedEvents: true
344 anchors.leftMargin: listView.leftMargin
345 anchors.rightMargin: listView.rightMargin
346 enabled: !root.readOnly
347
348 property int draggedIndex: -1
349
350 property int startX: 0
351 property int startY: 0
352
353 onMouseXChanged: {
354 if (!pressed || dragging) {
355 listView.progressiveScroll(mouseX)
356 }
357 }
358 onMouseYChanged: {
359 if (Math.abs(mouseY - startY) > units.gu(3)) {
360 drag.axis = Drag.XAndYAxis;
361 }
362 }
363
364 onReleased: {
365 var result = fakeDragItem.Drag.drop();
366 // if (result == Qt.IgnoreAction) {
367 // WorkspaceManager.destroyWorkspace(fakeDragItem.workspace);
368 // }
369 root.commitScreenSetup();
370 drag.target = null;
371 }
372
373 property bool dragging: drag.active
374 onDraggingChanged: {
375 if (drag.active) {
376 var ws = listView.model.get(draggedIndex);
377 if (ws) ws.unassign();
378 }
379 }
380
381 onPressed: {
382 startX = mouseX;
383 startY = mouseY;
384 if (listView.model.count < 2) return;
385
386 var coords = mapToItem(listView.contentItem, mouseX, mouseY)
387 draggedIndex = listView.indexAt(coords.x, coords.y)
388 var clickedItem = listView.itemAt(coords.x, coords.y)
389
390 var itemCoords = clickedItem.mapToItem(listView, -listView.leftMargin, 0);
391 fakeDragItem.x = itemCoords.x
392 fakeDragItem.y = itemCoords.y
393 fakeDragItem.workspace = listView.model.get(draggedIndex)
394
395 var mouseCoordsInItem = mapToItem(clickedItem, mouseX, mouseY);
396 fakeDragItem.Drag.hotSpot.x = mouseCoordsInItem.x
397 fakeDragItem.Drag.hotSpot.y = mouseCoordsInItem.y
398
399 drag.axis = Drag.YAxis;
400 drag.target = fakeDragItem;
401 }
402
403 WorkspacePreview {
404 id: fakeDragItem
405 height: listView.height
406 width: listView.itemWidth
407 screen: root.screen
408 background: root.background
409 screenHeight: listView.screenSpaceHeight
410 launcherWidth: listView.launcherWidth
411 visible: Drag.active
412
413 Drag.active: hoverMouseArea.drag.active
414 Drag.keys: ['workspace']
415
416 property bool inDropArea: false
417
418 Rectangle {
419 anchors.fill: parent
420 color: "#33000000"
421 opacity: parent.inDropArea ? 0 : 1
422 Behavior on opacity { LomiriNumberAnimation { } }
423 Rectangle {
424 anchors.centerIn: parent
425 width: units.gu(6)
426 height: units.gu(6)
427 radius: width / 2
428 color: "#aa000000"
429 }
430
431 Icon {
432 height: units.gu(3)
433 width: height
434 anchors.centerIn: parent
435 name: "edit-delete"
436 color: "white"
437 }
438 }
439
440 states: [
441 State {
442 when: fakeDragItem.Drag.active
443 ParentChange { target: fakeDragItem; parent: shell }
444 }
445 ]
446 }
447 }
448 }
449 }
450}