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