Unity 8
Stage.qml
1 /*
2  * Copyright (C) 2014-2016 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 Ubuntu.Components 1.3
19 import Unity.Application 0.1
20 import "../Components/PanelState"
21 import "../Components"
22 import Utils 0.1
23 import Ubuntu.Gestures 0.1
24 import GlobalShortcut 1.0
25 import GSettings 1.0
26 import "Spread"
27 import "Spread/MathUtils.js" as MathUtils
28 
29 FocusScope {
30  id: root
31  anchors.fill: parent
32 
33  property QtObject applicationManager
34  property QtObject topLevelSurfaceList
35  property bool altTabPressed
36  property url background
37  property int dragAreaWidth
38  property bool interactive
39  property real nativeHeight
40  property real nativeWidth
41  property QtObject orientations
42  property int shellOrientation
43  property int shellOrientationAngle
44  property bool spreadEnabled: true // If false, animations and right edge will be disabled
45  property bool suspended
46  property int leftMargin: 0
47  property bool oskEnabled: false
48  property rect inputMethodRect
49 
50  // Configuration
51  property string mode: "staged"
52 
53  // Used by the tutorial code
54  readonly property real rightEdgeDragProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0 // How far left the stage has been dragged
55 
56  // used by the snap windows (edge maximize) feature
57  readonly property alias previewRectangle: fakeRectangle
58 
59  readonly property bool spreadShown: state == "spread"
60  readonly property var mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
61 
62  // application windows never rotate independently
63  property int mainAppWindowOrientationAngle: shellOrientationAngle
64 
65  property bool orientationChangesEnabled: !priv.focusedAppDelegate || priv.focusedAppDelegate.orientationChangesEnabled
66 
67  property int supportedOrientations: {
68  if (mainApp) {
69  switch (mode) {
70  case "staged":
71  return mainApp.supportedOrientations;
72  case "stagedWithSideStage":
73  var orientations = mainApp.supportedOrientations;
74  orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
75  if (priv.sideStageItemId) {
76  // If we have a sidestage app, support Portrait orientation
77  // so that it will switch the sidestage app to mainstage on rotate to portrait
78  orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
79  }
80  return orientations;
81  }
82  }
83 
84  return Qt.PortraitOrientation |
85  Qt.LandscapeOrientation |
86  Qt.InvertedPortraitOrientation |
87  Qt.InvertedLandscapeOrientation;
88  }
89 
90 
91  onAltTabPressedChanged: {
92  if (altTabPressed) {
93  if (root.spreadEnabled) {
94  altTabDelayTimer.start();
95  }
96  } else {
97  // Alt Tab has been released, did we already go to spread?
98  if (priv.goneToSpread) {
99  priv.goneToSpread = false;
100  } else {
101  // No we didn't, do a quick alt-tab
102  if (appRepeater.count > 1) {
103  appRepeater.itemAt(1).claimFocus();
104  }
105  }
106  }
107  }
108 
109  Timer {
110  id: altTabDelayTimer
111  interval: 140
112  repeat: false
113  onTriggered: {
114  if (root.altTabPressed) {
115  priv.goneToSpread = true;
116  }
117  }
118  }
119 
120  property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
121  priv.focusedAppDelegate.clientAreaItem : null;
122 
123  signal itemSnapshotRequested(Item item)
124 
125  // functions to be called from outside
126  function updateFocusedAppOrientation() { /* TODO */ }
127  function updateFocusedAppOrientationAnimated() { /* TODO */}
128  function pushRightEdge(amount) {
129  if (root.spreadEnabled) {
130  edgeBarrier.push(amount);
131  }
132  }
133 
134  function closeSpread() {
135  priv.goneToSpread = false;
136  }
137 
138  onSpreadEnabledChanged: {
139  if (!spreadEnabled && spreadShown) {
140  closeSpread();
141  }
142  }
143 
144  GSettings {
145  id: lifecycleExceptions
146  schema.id: "com.canonical.qtmir"
147  }
148 
149  function isExemptFromLifecycle(appId) {
150  var shortAppId = appId.split('_')[0];
151  for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
152  if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
153  return true;
154  }
155  }
156  return false;
157  }
158 
159  GlobalShortcut {
160  id: closeFocusedShortcut
161  shortcut: Qt.AltModifier|Qt.Key_F4
162  onTriggered: {
163  if (priv.focusedAppDelegate && !priv.focusedAppDelegate.isDash) {
164  priv.focusedAppDelegate.close();
165  }
166  }
167  }
168 
169  GlobalShortcut {
170  id: showSpreadShortcut
171  shortcut: Qt.MetaModifier|Qt.Key_W
172  active: root.spreadEnabled
173  onTriggered: priv.goneToSpread = true
174  }
175 
176  GlobalShortcut {
177  id: minimizeAllShortcut
178  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
179  onTriggered: priv.minimizeAllWindows()
180  active: root.state == "windowed"
181  }
182 
183  GlobalShortcut {
184  id: maximizeWindowShortcut
185  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
186  onTriggered: priv.focusedAppDelegate.requestMaximize()
187  active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
188  }
189 
190  GlobalShortcut {
191  id: maximizeWindowLeftShortcut
192  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
193  onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
194  active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
195  }
196 
197  GlobalShortcut {
198  id: maximizeWindowRightShortcut
199  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
200  onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
201  active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
202  }
203 
204  GlobalShortcut {
205  id: minimizeRestoreShortcut
206  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
207  onTriggered: {
208  if (priv.focusedAppDelegate.anyMaximized) {
209  priv.focusedAppDelegate.requestRestore();
210  } else {
211  priv.focusedAppDelegate.requestMinimize();
212  }
213  }
214  active: root.state == "windowed" && priv.focusedAppDelegate
215  }
216 
217  GlobalShortcut {
218  shortcut: Qt.AltModifier|Qt.Key_Print
219  onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
220  active: priv.focusedAppDelegate !== null
221  }
222 
223  QtObject {
224  id: priv
225  objectName: "DesktopStagePrivate"
226 
227  property var focusedAppDelegate: null
228  property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
229 
230  property bool goneToSpread: false
231  property int closingIndex: -1
232  property int animationDuration: UbuntuAnimation.FastDuration
233 
234  function updateForegroundMaximizedApp() {
235  var found = false;
236  for (var i = 0; i < appRepeater.count && !found; i++) {
237  var item = appRepeater.itemAt(i);
238  if (item && item.visuallyMaximized) {
239  foregroundMaximizedAppDelegate = item;
240  found = true;
241  }
242  }
243  if (!found) {
244  foregroundMaximizedAppDelegate = null;
245  }
246  }
247 
248  function minimizeAllWindows() {
249  for (var i = appRepeater.count - 1; i >= 0; i--) {
250  var appDelegate = appRepeater.itemAt(i);
251  if (appDelegate && !appDelegate.minimized) {
252  appDelegate.requestMinimize();
253  }
254  }
255  }
256 
257  readonly property bool sideStageEnabled: root.mode === "stagedWithSideStage" &&
258  (root.shellOrientation == Qt.LandscapeOrientation ||
259  root.shellOrientation == Qt.InvertedLandscapeOrientation)
260  onSideStageEnabledChanged: {
261  for (var i = 0; i < appRepeater.count; i++) {
262  appRepeater.itemAt(i).refreshStage();
263  }
264  priv.updateMainAndSideStageIndexes();
265  }
266 
267  property var mainStageDelegate: null
268  property var sideStageDelegate: null
269  property int mainStageItemId: 0
270  property int sideStageItemId: 0
271  property string mainStageAppId: ""
272  property string sideStageAppId: ""
273 
274  onSideStageDelegateChanged: {
275  if (!sideStageDelegate) {
276  sideStage.hide();
277  }
278  }
279 
280  function updateMainAndSideStageIndexes() {
281  if (root.mode != "stagedWithSideStage") {
282  priv.sideStageDelegate = null;
283  priv.sideStageItemId = 0;
284  priv.sideStageAppId = "";
285  priv.mainStageDelegate = appRepeater.itemAt(0);
286  priv.mainStageItemId = topLevelSurfaceList.idAt(0);
287  priv.mainStageAppId = topLevelSurfaceList.applicationAt(0) ? topLevelSurfaceList.applicationAt(0).appId : ""
288  return;
289  }
290 
291  var choseMainStage = false;
292  var choseSideStage = false;
293 
294  if (!root.topLevelSurfaceList)
295  return;
296 
297  for (var i = 0; i < appRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
298  var appDelegate = appRepeater.itemAt(i);
299  if (!appDelegate) {
300  // This might happen during startup phase... If the delegate appears and claims focus
301  // things are updated and appRepeater.itemAt(x) still returns null while appRepeater.count >= x
302  // Lets just skip it, on startup it will be generated at a later point too...
303  continue;
304  }
305  if (sideStage.shown && appDelegate.stage == ApplicationInfoInterface.SideStage
306  && !choseSideStage) {
307  priv.sideStageDelegate = appDelegate
308  priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
309  priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
310  choseSideStage = true;
311  } else if (!choseMainStage && appDelegate.stage == ApplicationInfoInterface.MainStage) {
312  priv.mainStageDelegate = appDelegate;
313  priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
314  priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
315  choseMainStage = true;
316  }
317  }
318  if (!choseMainStage && priv.mainStageDelegate) {
319  priv.mainStageDelegate = null;
320  priv.mainStageItemId = 0;
321  priv.mainStageAppId = "";
322  }
323  if (!choseSideStage && priv.sideStageDelegate) {
324  priv.sideStageDelegate = null;
325  priv.sideStageItemId = 0;
326  priv.sideStageAppId = "";
327  }
328  }
329 
330  property int nextInStack: {
331  var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.itemIndex : -1;
332  var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.itemIndex : -1;
333  if (sideStageIndex == -1) {
334  return topLevelSurfaceList.count > 1 ? 1 : -1;
335  }
336  if (mainStageIndex == 0 || sideStageIndex == 0) {
337  if (mainStageIndex == 1 || sideStageIndex == 1) {
338  return topLevelSurfaceList.count > 2 ? 2 : -1;
339  }
340  return 1;
341  }
342  return -1;
343  }
344 
345  readonly property real virtualKeyboardHeight: root.inputMethodRect.height
346  }
347 
348  Component.onCompleted: priv.updateMainAndSideStageIndexes();
349 
350  Connections {
351  target: PanelState
352  onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
353  onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
354  onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
355  }
356 
357  Binding {
358  target: PanelState
359  property: "buttonsVisible"
360  value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
361  }
362 
363  Binding {
364  target: PanelState
365  property: "title"
366  value: {
367  if (priv.focusedAppDelegate !== null) {
368  if (priv.focusedAppDelegate.maximized)
369  return priv.focusedAppDelegate.title
370  else
371  return priv.focusedAppDelegate.appName
372  }
373  return ""
374  }
375  when: priv.focusedAppDelegate
376  }
377 
378  Binding {
379  target: PanelState
380  property: "dropShadow"
381  value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
382  }
383 
384  Binding {
385  target: PanelState
386  property: "closeButtonShown"
387  value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
388  }
389 
390  Component.onDestruction: {
391  PanelState.title = "";
392  PanelState.buttonsVisible = false;
393  PanelState.dropShadow = false;
394  }
395 
396  Instantiator {
397  model: root.applicationManager
398  delegate: QtObject {
399  property var stateBinding: Binding {
400  readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
401  target: model.application
402  property: "requestedState"
403 
404  // TODO: figure out some lifecycle policy, like suspending minimized apps
405  // or something if running windowed.
406  // TODO: If the device has a dozen suspended apps because it was running
407  // in staged mode, when it switches to Windowed mode it will suddenly
408  // resume all those apps at once. We might want to avoid that.
409  value: root.mode === "windowed"
410  || isDash
411  || (!root.suspended && model.application && priv.focusedAppDelegate &&
412  (priv.focusedAppDelegate.appId === model.application.appId ||
413  priv.mainStageAppId === model.application.appId ||
414  priv.sideStageAppId === model.application.appId))
415  ? ApplicationInfoInterface.RequestedRunning
416  : ApplicationInfoInterface.RequestedSuspended
417  }
418 
419  property var lifecycleBinding: Binding {
420  target: model.application
421  property: "exemptFromLifecycle"
422  value: model.application
423  ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
424  : false
425  }
426  }
427  }
428 
429  states: [
430  State {
431  name: "spread"; when: priv.goneToSpread
432  PropertyChanges { target: floatingFlickable; enabled: true }
433  PropertyChanges { target: spreadItem; focus: true }
434  PropertyChanges { target: hoverMouseArea; enabled: true }
435  PropertyChanges { target: rightEdgeDragArea; enabled: false }
436  PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
437  PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
438  PropertyChanges { target: wallpaper; visible: false }
439  },
440  State {
441  name: "stagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "staged"
442  PropertyChanges {
443  target: blurLayer;
444  visible: true;
445  blurRadius: 32
446  brightness: .65
447  opacity: 1
448  }
449  },
450  State {
451  name: "sideStagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "stagedWithSideStage"
452  extend: "stagedRightEdge"
453  PropertyChanges {
454  target: sideStage
455  opacity: priv.sideStageDelegate.x === sideStage.x ? 1 : 0
456  visible: true
457  }
458  },
459  State {
460  name: "windowedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "windowed"
461  PropertyChanges {
462  target: blurLayer;
463  visible: true
464  blurRadius: 32
465  brightness: .65
466  opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, edgeBarrier.progress))
467  }
468  },
469  State {
470  name: "staged"; when: root.mode === "staged"
471  PropertyChanges { target: wallpaper; visible: false }
472  },
473  State {
474  name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
475  PropertyChanges { target: triGestureArea; enabled: priv.sideStageEnabled }
476  PropertyChanges { target: sideStage; visible: true }
477  },
478  State {
479  name: "windowed"; when: root.mode === "windowed"
480  }
481  ]
482  transitions: [
483  Transition {
484  from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
485  PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
486  PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
487  },
488  Transition {
489  to: "spread"
490  PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
491  PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
492  },
493  Transition {
494  from: "spread"
495  SequentialAnimation {
496  ScriptAction {
497  script: {
498  var item = appRepeater.itemAt(Math.max(0, spreadItem.highlightedIndex));
499  if (item.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
500  sideStage.show();
501  }
502  item.playFocusAnimation();
503  }
504  }
505  PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
506  }
507  },
508  Transition {
509  to: "stagedRightEdge,sideStagedRightEdge"
510  PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
511  },
512  Transition {
513  to: "stagedWithSideStage"
514  ScriptAction { script: priv.updateMainAndSideStageIndexes(); }
515  }
516 
517  ]
518 
519  MouseArea {
520  id: cancelSpreadMouseArea
521  anchors.fill: parent
522  enabled: false
523  onClicked: priv.goneToSpread = false
524  }
525 
526  FocusScope {
527  id: appContainer
528  objectName: "appContainer"
529  anchors.fill: parent
530  focus: true
531 
532  Wallpaper {
533  id: wallpaper
534  anchors.fill: parent
535  source: root.background
536  // Make sure it's the lowest item. Due to the left edge drag we sometimes need
537  // to put the dash at -1 and we don't want it behind the Wallpaper
538  z: -2
539  }
540 
541  BlurLayer {
542  id: blurLayer
543  anchors.fill: parent
544  source: wallpaper
545  visible: false
546  }
547 
548  Spread {
549  id: spreadItem
550  objectName: "spreadItem"
551  anchors.fill: appContainer
552  leftMargin: root.leftMargin
553  model: root.topLevelSurfaceList
554  spreadFlickable: floatingFlickable
555  z: 10
556 
557  onLeaveSpread: {
558  priv.goneToSpread = false;
559  }
560  }
561 
562  Connections {
563  target: root.topLevelSurfaceList
564  onListChanged: priv.updateMainAndSideStageIndexes()
565  }
566 
567 
568  DropArea {
569  objectName: "MainStageDropArea"
570  anchors {
571  left: parent.left
572  top: parent.top
573  bottom: parent.bottom
574  }
575  width: appContainer.width - sideStage.width
576  enabled: priv.sideStageEnabled
577 
578  onDropped: {
579  drop.source.appDelegate.saveStage(ApplicationInfoInterface.MainStage);
580  drop.source.appDelegate.focus = true;
581  }
582  keys: "SideStage"
583  }
584 
585  SideStage {
586  id: sideStage
587  objectName: "sideStage"
588  shown: false
589  height: appContainer.height
590  x: appContainer.width - width
591  visible: false
592  Behavior on opacity { UbuntuNumberAnimation {} }
593  z: {
594  if (!priv.mainStageItemId) return 0;
595 
596  if (priv.sideStageItemId && priv.nextInStack > 0) {
597 
598  // Due the order in which bindings are evaluated, this might be triggered while shuffling
599  // the list and index doesn't yet match with itemIndex (even though itemIndex: index)
600  // Let's walk the list and compare itemIndex to make sure we have the correct one.
601  var nextDelegateInStack = -1;
602  for (var i = 0; i < appRepeater.count; i++) {
603  if (appRepeater.itemAt(i).itemIndex == priv.nextInStack) {
604  nextDelegateInStack = appRepeater.itemAt(i);
605  break;
606  }
607  }
608 
609  if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
610  // if the next app in stack is a main stage app, put the sidestage on top of it.
611  return 2;
612  }
613  return 1;
614  }
615 
616  return 1;
617  }
618 
619  onShownChanged: {
620  if (!shown && priv.mainStageDelegate && !root.spreadShown) {
621  priv.mainStageDelegate.activate();
622  }
623  }
624 
625  DropArea {
626  id: sideStageDropArea
627  objectName: "SideStageDropArea"
628  anchors.fill: parent
629 
630  property bool dropAllowed: true
631 
632  onEntered: {
633  dropAllowed = drag.keys != "Disabled";
634  }
635  onExited: {
636  dropAllowed = true;
637  }
638  onDropped: {
639  if (drop.keys == "MainStage") {
640  drop.source.appDelegate.saveStage(ApplicationInfoInterface.SideStage);
641  drop.source.appDelegate.focus = true;
642  }
643  }
644  drag {
645  onSourceChanged: {
646  if (!sideStageDropArea.drag.source) {
647  dropAllowed = true;
648  }
649  }
650  }
651  }
652  }
653 
654  Repeater {
655  id: appRepeater
656  model: topLevelSurfaceList
657  objectName: "appRepeater"
658 
659  function indexOf(delegateItem) {
660  for (var i = 0; i < count; i++) {
661  if (itemAt(i) === delegateItem) {
662  return i;
663  }
664  }
665  return -1;
666  }
667 
668  delegate: FocusScope {
669  id: appDelegate
670  objectName: "appDelegate_" + model.window.id
671  property int itemIndex: index // We need this from outside the repeater
672  // z might be overriden in some cases by effects, but we need z ordering
673  // to calculate occlusion detection
674  property int normalZ: topLevelSurfaceList.count - index
675  onNormalZChanged: {
676  if (visuallyMaximized) {
677  priv.updateForegroundMaximizedApp();
678  }
679  }
680  z: normalZ
681 
682  // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
683  // match what the actual surface size is.
684  // Don't write to those, they will be set by states
685  x: model.window.position.x - clientAreaItem.x
686  y: model.window.position.y - clientAreaItem.y
687  width: decoratedWindow.implicitWidth
688  height: decoratedWindow.implicitHeight
689 
690  // requestedX/Y/width/height is what we ask the actual surface to be.
691  // Do not write to those, they will be set by states
692  property real requestedX: windowedX
693  property real requestedY: windowedY
694  property real requestedWidth: windowedWidth
695  property real requestedHeight: windowedHeight
696  Binding {
697  target: model.window; property: "requestedPosition"
698  // miral doesn't know about our window decorations. So we have to deduct them
699  value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
700  appDelegate.requestedY + appDelegate.clientAreaItem.y)
701  }
702 
703  // In those are for windowed mode. Those values basically store the window's properties
704  // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
705  property real windowedX
706  property real windowedY
707  property real windowedWidth
708  property real windowedHeight
709 
710  // unlike windowedX/Y, this is the last known grab position before being pushed against edges/corners
711  // when restoring, the window should return to these, not to the place where it was dropped near the edge
712  property real restoredX
713  property real restoredY
714 
715  // Keeps track of the window geometry while in normal or restored state
716  // Useful when returning from some maxmized state or when saving the geometry while maximized
717  // FIXME: find a better solution
718  property real normalX: 0
719  property real normalY: 0
720  property real normalWidth: 0
721  property real normalHeight: 0
722  function updateNormalGeometry() {
723  if (appDelegate.state == "normal" || appDelegate.state == "restored") {
724  normalX = appDelegate.requestedX;
725  normalY = appDelegate.requestedY;
726  normalWidth = appDelegate.width;
727  normalHeight = appDelegate.height;
728  }
729  }
730  Connections {
731  target: appDelegate
732  onXChanged: appDelegate.updateNormalGeometry();
733  onYChanged: appDelegate.updateNormalGeometry();
734  onWidthChanged: appDelegate.updateNormalGeometry();
735  onHeightChanged: appDelegate.updateNormalGeometry();
736  }
737 
738  Binding {
739  target: appDelegate
740  property: "y"
741  value: appDelegate.requestedY -
742  Math.min(appDelegate.requestedY - PanelState.panelHeight,
743  Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
744  when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
745  && root.inputMethodRect.height > 0
746 
747  }
748 
749  Behavior on x { id: xBehavior; enabled: priv.closingIndex >= 0; UbuntuNumberAnimation { onRunningChanged: if (!running) priv.closingIndex = -1} }
750 
751  Connections {
752  target: root
753  onShellOrientationAngleChanged: {
754  // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
755  if (application && application.rotatesWindowContents) {
756  if (root.state == "windowed") {
757  var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
758  angleDiff = (360 + angleDiff) % 360;
759  if (angleDiff === 90 || angleDiff === 270) {
760  var aux = decoratedWindow.requestedHeight;
761  decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.decorationHeight;
762  decoratedWindow.requestedWidth = aux - decoratedWindow.decorationHeight;
763  }
764  }
765  decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
766  } else {
767  decoratedWindow.surfaceOrientationAngle = 0;
768  }
769  }
770  }
771 
772  readonly property alias application: decoratedWindow.application
773  readonly property alias minimumWidth: decoratedWindow.minimumWidth
774  readonly property alias minimumHeight: decoratedWindow.minimumHeight
775  readonly property alias maximumWidth: decoratedWindow.maximumWidth
776  readonly property alias maximumHeight: decoratedWindow.maximumHeight
777  readonly property alias widthIncrement: decoratedWindow.widthIncrement
778  readonly property alias heightIncrement: decoratedWindow.heightIncrement
779 
780  readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
781  readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
782  readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
783  readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
784  readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
785  readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
786  readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
787  readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
788  readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
789  readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
790  maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
791 
792  readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
793  readonly property bool fullscreen: window.state === Mir.FullscreenState
794 
795  readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
796  readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
797  (maximumHeight == 0 || maximumHeight >= appContainer.height)
798  readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
799  (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
800  readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
801  readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
802  readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
803 
804  property int windowState: WindowStateStorage.WindowStateNormal
805  property bool animationsEnabled: true
806  property alias title: decoratedWindow.title
807  readonly property string appName: model.application ? model.application.name : ""
808  property bool visuallyMaximized: false
809  property bool visuallyMinimized: false
810  readonly property alias windowedTransitionRunning: windowedTransition.running
811 
812  property int stage: ApplicationInfoInterface.MainStage
813  function saveStage(newStage) {
814  appDelegate.stage = newStage;
815  WindowStateStorage.saveStage(appId, newStage);
816  priv.updateMainAndSideStageIndexes()
817  }
818 
819  readonly property var surface: model.window.surface
820  readonly property var window: model.window
821 
822  readonly property alias resizeArea: resizeArea
823  readonly property alias focusedSurface: decoratedWindow.focusedSurface
824  readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
825 
826  readonly property string appId: model.application.appId
827  readonly property bool isDash: appId == "unity8-dash"
828  readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
829 
830  function activate() {
831  if (model.window.focused) {
832  updateQmlFocusFromMirSurfaceFocus();
833  } else {
834  model.window.activate();
835  }
836  }
837  function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
838  function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
839  function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
840  function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
841  function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
842  function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
843  function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
844  function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
845  function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
846  function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
847  function requestRestore() { model.window.requestState(Mir.RestoredState); }
848 
849  function claimFocus() {
850  if (root.state == "spread") {
851  spreadItem.highlightedIndex = index
852  priv.goneToSpread = false;
853  }
854  if (root.mode == "stagedWithSideStage") {
855  if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
856  sideStage.show();
857  }
858  priv.updateMainAndSideStageIndexes();
859  }
860  if (root.mode == "windowed") {
861  appDelegate.restore(true /* animated */, appDelegate.windowState);
862  }
863  appDelegate.focus = true;
864  }
865 
866  function updateQmlFocusFromMirSurfaceFocus() {
867  if (model.window.focused) {
868  claimFocus();
869  priv.focusedAppDelegate = appDelegate;
870  }
871  }
872 
873  WindowStateSaver {
874  id: windowStateSaver
875  target: appDelegate
876  screenWidth: appContainer.width
877  screenHeight: appContainer.height
878  leftMargin: root.leftMargin
879  minimumY: PanelState.panelHeight
880  }
881 
882  Connections {
883  target: model.window
884  onFocusedChanged: {
885  updateQmlFocusFromMirSurfaceFocus();
886  }
887  onFocusRequested: {
888  appDelegate.activate();
889  }
890  onStateChanged: {
891  if (model.window.state === Mir.MinimizedState) {
892  appDelegate.minimize();
893  } else if (model.window.state === Mir.MaximizedState) {
894  appDelegate.maximize();
895  } else if (model.window.state === Mir.VertMaximizedState) {
896  appDelegate.maximizeVertically();
897  } else if (model.window.state === Mir.HorizMaximizedState) {
898  appDelegate.maximizeHorizontally();
899  } else if (model.window.state === Mir.MaximizedLeftState) {
900  appDelegate.maximizeLeft();
901  } else if (model.window.state === Mir.MaximizedRightState) {
902  appDelegate.maximizeRight();
903  } else if (model.window.state === Mir.MaximizedTopLeftState) {
904  appDelegate.maximizeTopLeft();
905  } else if (model.window.state === Mir.MaximizedTopRightState) {
906  appDelegate.maximizeTopRight();
907  } else if (model.window.state === Mir.MaximizedBottomLeftState) {
908  appDelegate.maximizeBottomLeft();
909  } else if (model.window.state === Mir.MaximizedBottomRightState) {
910  appDelegate.maximizeBottomRight();
911  } else if (model.window.state === Mir.RestoredState) {
912  appDelegate.restore();
913  }
914  }
915  }
916 
917  Component.onCompleted: {
918  if (application && application.rotatesWindowContents) {
919  decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
920  } else {
921  decoratedWindow.surfaceOrientationAngle = 0;
922  }
923 
924  // First, cascade the newly created window, relative to the currently/old focused window.
925  windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
926  windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
927  // Now load any saved state. This needs to happen *after* the cascading!
928  windowStateSaver.load();
929  model.window.requestState(WindowStateStorage.toMirState(windowState));
930 
931  updateQmlFocusFromMirSurfaceFocus();
932 
933  refreshStage();
934  _constructing = false;
935  }
936  Component.onDestruction: {
937  windowStateSaver.save();
938 
939  if (!root.parent) {
940  // This stage is about to be destroyed. Don't mess up with the model at this point
941  return;
942  }
943 
944  if (visuallyMaximized) {
945  priv.updateForegroundMaximizedApp();
946  }
947  }
948 
949  onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
950 
951  property bool _constructing: true;
952  onStageChanged: {
953  if (!_constructing) {
954  priv.updateMainAndSideStageIndexes();
955  }
956  }
957 
958  visible: (
959  !visuallyMinimized
960  && !greeter.fullyShown
961  && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
962  )
963  || appDelegate.fullscreen
964  || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
965 
966  function close() {
967  model.window.close();
968  }
969 
970  function maximize(animated) {
971  animationsEnabled = (animated === undefined) || animated;
972  windowState = WindowStateStorage.WindowStateMaximized;
973  }
974  function maximizeLeft(animated) {
975  animationsEnabled = (animated === undefined) || animated;
976  windowState = WindowStateStorage.WindowStateMaximizedLeft;
977  }
978  function maximizeRight(animated) {
979  animationsEnabled = (animated === undefined) || animated;
980  windowState = WindowStateStorage.WindowStateMaximizedRight;
981  }
982  function maximizeHorizontally(animated) {
983  animationsEnabled = (animated === undefined) || animated;
984  windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
985  }
986  function maximizeVertically(animated) {
987  animationsEnabled = (animated === undefined) || animated;
988  windowState = WindowStateStorage.WindowStateMaximizedVertically;
989  }
990  function maximizeTopLeft(animated) {
991  animationsEnabled = (animated === undefined) || animated;
992  windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
993  }
994  function maximizeTopRight(animated) {
995  animationsEnabled = (animated === undefined) || animated;
996  windowState = WindowStateStorage.WindowStateMaximizedTopRight;
997  }
998  function maximizeBottomLeft(animated) {
999  animationsEnabled = (animated === undefined) || animated;
1000  windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
1001  }
1002  function maximizeBottomRight(animated) {
1003  animationsEnabled = (animated === undefined) || animated;
1004  windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
1005  }
1006  function minimize(animated) {
1007  animationsEnabled = (animated === undefined) || animated;
1008  windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
1009  }
1010  function restore(animated,state) {
1011  animationsEnabled = (animated === undefined) || animated;
1012  windowState = state || WindowStateStorage.WindowStateRestored;
1013  windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1014  }
1015 
1016  function playFocusAnimation() {
1017  if (state == "stagedRightEdge") {
1018  // TODO: Can we drop this if and find something that always works?
1019  if (root.mode == "staged") {
1020  rightEdgeFocusAnimation.targetX = 0
1021  rightEdgeFocusAnimation.start()
1022  } else if (root.mode == "stagedWithSideStage") {
1023  rightEdgeFocusAnimation.targetX = appDelegate.stage == ApplicationInfoInterface.SideStage ? sideStage.x : 0
1024  rightEdgeFocusAnimation.start()
1025  }
1026  } else if (state == "windowedRightEdge" || state == "windowed") {
1027  activate();
1028  } else {
1029  focusAnimation.start()
1030  }
1031  }
1032  function playHidingAnimation() {
1033  if (state != "windowedRightEdge") {
1034  hidingAnimation.start()
1035  }
1036  }
1037 
1038  function refreshStage() {
1039  var newStage = ApplicationInfoInterface.MainStage;
1040  if (priv.sideStageEnabled) { // we're in lanscape rotation.
1041  if (!isDash && application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1042  var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
1043  if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
1044  // if it supports lanscape, it defaults to mainstage.
1045  defaultStage = ApplicationInfoInterface.MainStage;
1046  }
1047  newStage = WindowStateStorage.getStage(application.appId, defaultStage);
1048  }
1049  }
1050 
1051  stage = newStage;
1052  if (focus && stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1053  sideStage.show();
1054  }
1055  }
1056 
1057  UbuntuNumberAnimation {
1058  id: focusAnimation
1059  target: appDelegate
1060  property: "scale"
1061  from: 0.98
1062  to: 1
1063  duration: UbuntuAnimation.SnapDuration
1064  onStarted: {
1065  topLevelSurfaceList.raiseId(model.window.id);
1066  }
1067  onStopped: {
1068  appDelegate.activate();
1069  }
1070  }
1071  ParallelAnimation {
1072  id: rightEdgeFocusAnimation
1073  property int targetX: 0
1074  UbuntuNumberAnimation { target: appDelegate; properties: "x"; to: rightEdgeFocusAnimation.targetX; duration: priv.animationDuration }
1075  UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1076  UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1077  onStopped: {
1078  appDelegate.activate();
1079  }
1080  }
1081  ParallelAnimation {
1082  id: hidingAnimation
1083  UbuntuNumberAnimation { target: appDelegate; property: "opacity"; to: 0; duration: priv.animationDuration }
1084  onStopped: appDelegate.opacity = 1
1085  }
1086 
1087  SpreadMaths {
1088  id: spreadMaths
1089  spread: spreadItem
1090  itemIndex: index
1091  flickable: floatingFlickable
1092  }
1093  StageMaths {
1094  id: stageMaths
1095  sceneWidth: root.width
1096  stage: appDelegate.stage
1097  thisDelegate: appDelegate
1098  mainStageDelegate: priv.mainStageDelegate
1099  sideStageDelegate: priv.sideStageDelegate
1100  sideStageWidth: sideStage.panelWidth
1101  sideStageX: sideStage.x
1102  itemIndex: appDelegate.itemIndex
1103  nextInStack: priv.nextInStack
1104  }
1105 
1106  StagedRightEdgeMaths {
1107  id: stagedRightEdgeMaths
1108  sceneWidth: appContainer.width - root.leftMargin
1109  sceneHeight: appContainer.height
1110  isMainStageApp: priv.mainStageDelegate == appDelegate
1111  isSideStageApp: priv.sideStageDelegate == appDelegate
1112  sideStageWidth: sideStage.width
1113  sideStageOpen: sideStage.shown
1114  itemIndex: index
1115  nextInStack: priv.nextInStack
1116  progress: 0
1117  targetHeight: spreadItem.stackHeight
1118  targetX: spreadMaths.targetX
1119  startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1120  targetY: spreadMaths.targetY
1121  targetAngle: spreadMaths.targetAngle
1122  targetScale: spreadMaths.targetScale
1123  shuffledZ: stageMaths.itemZ
1124  breakPoint: spreadItem.rightEdgeBreakPoint
1125  }
1126 
1127  WindowedRightEdgeMaths {
1128  id: windowedRightEdgeMaths
1129  itemIndex: index
1130  startWidth: appDelegate.requestedWidth
1131  startHeight: appDelegate.requestedHeight
1132  targetHeight: spreadItem.stackHeight
1133  targetX: spreadMaths.targetX
1134  targetY: spreadMaths.targetY
1135  normalZ: appDelegate.normalZ
1136  targetAngle: spreadMaths.targetAngle
1137  targetScale: spreadMaths.targetScale
1138  breakPoint: spreadItem.rightEdgeBreakPoint
1139  }
1140 
1141  states: [
1142  State {
1143  name: "spread"; when: root.state == "spread"
1144  PropertyChanges {
1145  target: decoratedWindow;
1146  showDecoration: false;
1147  angle: spreadMaths.targetAngle
1148  itemScale: spreadMaths.targetScale
1149  scaleToPreviewSize: spreadItem.stackHeight
1150  scaleToPreviewProgress: 1
1151  hasDecoration: root.mode === "windowed"
1152  shadowOpacity: spreadMaths.shadowOpacity
1153  showHighlight: spreadItem.highlightedIndex === index
1154  darkening: spreadItem.highlightedIndex >= 0
1155  anchors.topMargin: dragArea.distance
1156  interactive: false
1157  }
1158  PropertyChanges {
1159  target: appDelegate
1160  x: spreadMaths.targetX
1161  y: spreadMaths.targetY
1162  z: index
1163  height: spreadItem.spreadItemHeight
1164  requestedWidth: decoratedWindow.oldRequestedWidth
1165  requestedHeight: decoratedWindow.oldRequestedHeight
1166  visible: spreadMaths.itemVisible
1167  }
1168  PropertyChanges { target: dragArea; enabled: true }
1169  PropertyChanges { target: windowInfoItem; opacity: spreadMaths.tileInfoOpacity; visible: spreadMaths.itemVisible }
1170  },
1171  State {
1172  name: "stagedRightEdge"
1173  when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1174  PropertyChanges {
1175  target: stagedRightEdgeMaths
1176  progress: Math.max(edgeBarrier.progress, rightEdgeDragArea.draggedProgress)
1177  }
1178  PropertyChanges {
1179  target: appDelegate
1180  x: stagedRightEdgeMaths.animatedX
1181  y: stagedRightEdgeMaths.animatedY
1182  z: stagedRightEdgeMaths.animatedZ
1183  height: stagedRightEdgeMaths.animatedHeight
1184  requestedWidth: decoratedWindow.oldRequestedWidth
1185  requestedHeight: decoratedWindow.oldRequestedHeight
1186  visible: appDelegate.x < root.width
1187  }
1188  PropertyChanges {
1189  target: decoratedWindow
1190  hasDecoration: false
1191  angle: stagedRightEdgeMaths.animatedAngle
1192  itemScale: stagedRightEdgeMaths.animatedScale
1193  scaleToPreviewSize: spreadItem.stackHeight
1194  scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1195  shadowOpacity: .3
1196  interactive: false
1197  }
1198  // make sure it's visible but transparent so it fades in when we transition to spread
1199  PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
1200  },
1201  State {
1202  name: "windowedRightEdge"
1203  when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || edgeBarrier.progress > 0)
1204  PropertyChanges {
1205  target: windowedRightEdgeMaths
1206  swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1207  pushProgress: edgeBarrier.progress
1208  }
1209  PropertyChanges {
1210  target: appDelegate
1211  x: windowedRightEdgeMaths.animatedX
1212  y: windowedRightEdgeMaths.animatedY
1213  z: windowedRightEdgeMaths.animatedZ
1214  height: stagedRightEdgeMaths.animatedHeight
1215  requestedWidth: decoratedWindow.oldRequestedWidth
1216  requestedHeight: decoratedWindow.oldRequestedHeight
1217  }
1218  PropertyChanges {
1219  target: decoratedWindow
1220  showDecoration: windowedRightEdgeMaths.decorationHeight
1221  angle: windowedRightEdgeMaths.animatedAngle
1222  itemScale: windowedRightEdgeMaths.animatedScale
1223  scaleToPreviewSize: spreadItem.stackHeight
1224  scaleToPreviewProgress: windowedRightEdgeMaths.scaleToPreviewProgress
1225  shadowOpacity: .3
1226  }
1227  PropertyChanges {
1228  target: opacityEffect;
1229  opacityValue: windowedRightEdgeMaths.opacityMask
1230  sourceItem: windowedRightEdgeMaths.opacityMask < 1 ? decoratedWindow : null
1231  }
1232  },
1233  State {
1234  name: "staged"; when: root.state == "staged"
1235  PropertyChanges {
1236  target: appDelegate
1237  x: stageMaths.itemX
1238  y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1239  requestedWidth: appContainer.width
1240  requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1241  visuallyMaximized: true
1242  visible: appDelegate.x < root.width
1243  }
1244  PropertyChanges {
1245  target: decoratedWindow
1246  hasDecoration: false
1247  }
1248  PropertyChanges {
1249  target: resizeArea
1250  enabled: false
1251  }
1252  PropertyChanges {
1253  target: stageMaths
1254  animateX: !focusAnimation.running && itemIndex !== spreadItem.highlightedIndex
1255  }
1256  },
1257  State {
1258  name: "stagedWithSideStage"; when: root.state == "stagedWithSideStage"
1259  PropertyChanges {
1260  target: stageMaths
1261  itemIndex: index
1262  }
1263  PropertyChanges {
1264  target: appDelegate
1265  x: stageMaths.itemX
1266  y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1267  z: stageMaths.itemZ
1268  requestedWidth: stageMaths.itemWidth
1269  requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1270  visuallyMaximized: true
1271  visible: appDelegate.x < root.width
1272  }
1273  PropertyChanges {
1274  target: decoratedWindow
1275  hasDecoration: false
1276  }
1277  PropertyChanges {
1278  target: resizeArea
1279  enabled: false
1280  }
1281  },
1282  State {
1283  name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
1284  PropertyChanges {
1285  target: appDelegate;
1286  requestedX: root.leftMargin;
1287  requestedY: 0;
1288  visuallyMinimized: false;
1289  visuallyMaximized: true
1290  requestedWidth: appContainer.width - root.leftMargin;
1291  requestedHeight: appContainer.height;
1292  }
1293  PropertyChanges { target: touchControls; enabled: true }
1294  },
1295  State {
1296  name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
1297  PropertyChanges {
1298  target: appDelegate;
1299  requestedX: 0
1300  requestedY: 0
1301  requestedWidth: appContainer.width;
1302  requestedHeight: appContainer.height;
1303  }
1304  PropertyChanges { target: decoratedWindow; hasDecoration: false }
1305  },
1306  State {
1307  name: "normal";
1308  when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
1309  PropertyChanges {
1310  target: appDelegate
1311  visuallyMinimized: false
1312  visuallyMaximized: false
1313  }
1314  PropertyChanges { target: touchControls; enabled: true }
1315  PropertyChanges { target: resizeArea; enabled: true }
1316  PropertyChanges { target: decoratedWindow; shadowOpacity: .3}
1317  },
1318  State {
1319  name: "restored";
1320  when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1321  extend: "normal"
1322  PropertyChanges {
1323  target: appDelegate;
1324  windowedX: restoredX;
1325  windowedY: restoredY;
1326  }
1327  },
1328  State {
1329  name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1330  extend: "normal"
1331  PropertyChanges {
1332  target: appDelegate
1333  windowedX: root.leftMargin
1334  windowedY: PanelState.panelHeight
1335  windowedWidth: (appContainer.width - root.leftMargin)/2
1336  windowedHeight: appContainer.height - PanelState.panelHeight
1337  }
1338  },
1339  State {
1340  name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1341  extend: "maximizedLeft"
1342  PropertyChanges {
1343  target: appDelegate;
1344  windowedX: (appContainer.width + root.leftMargin)/2
1345  }
1346  },
1347  State {
1348  name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1349  extend: "normal"
1350  PropertyChanges {
1351  target: appDelegate
1352  windowedX: root.leftMargin
1353  windowedY: PanelState.panelHeight
1354  windowedWidth: (appContainer.width - root.leftMargin)/2
1355  windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1356  }
1357  },
1358  State {
1359  name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1360  extend: "maximizedTopLeft"
1361  PropertyChanges {
1362  target: appDelegate
1363  windowedX: (appContainer.width + root.leftMargin)/2
1364  }
1365  },
1366  State {
1367  name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1368  extend: "normal"
1369  PropertyChanges {
1370  target: appDelegate
1371  windowedX: root.leftMargin
1372  windowedY: (appContainer.height + PanelState.panelHeight)/2
1373  windowedWidth: (appContainer.width - root.leftMargin)/2
1374  windowedHeight: appContainer.height/2
1375  }
1376  },
1377  State {
1378  name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1379  extend: "maximizedBottomLeft"
1380  PropertyChanges {
1381  target: appDelegate
1382  windowedX: (appContainer.width + root.leftMargin)/2
1383  }
1384  },
1385  State {
1386  name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1387  extend: "normal"
1388  PropertyChanges { target: appDelegate; windowedX: root.leftMargin; windowedY: windowedY;
1389  windowedWidth: appContainer.width - root.leftMargin; windowedHeight: windowedHeight }
1390  },
1391  State {
1392  name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1393  extend: "normal"
1394  PropertyChanges { target: appDelegate; windowedX: windowedX; windowedY: PanelState.panelHeight;
1395  windowedWidth: windowedWidth; windowedHeight: appContainer.height - PanelState.panelHeight }
1396  },
1397  State {
1398  name: "minimized"; when: appDelegate.minimized
1399  PropertyChanges {
1400  target: appDelegate
1401  requestedX: -appDelegate.width / 2
1402  scale: units.gu(5) / appDelegate.width
1403  opacity: 0;
1404  visuallyMinimized: true
1405  visuallyMaximized: false
1406  }
1407  }
1408  ]
1409  transitions: [
1410  Transition {
1411  from: "staged,stagedWithSideStage"; to: "normal"
1412  enabled: appDelegate.animationsEnabled
1413  PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1414  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
1415  },
1416  Transition {
1417  from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight";
1418  to: "staged,stagedWithSideStage"
1419  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1420  },
1421  Transition {
1422  to: "minimized"
1423  enabled: appDelegate.animationsEnabled
1424  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1425  SequentialAnimation {
1426  UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1427  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1428  }
1429  },
1430  Transition {
1431  to: "spread"
1432  // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1433  PropertyAction { target: appDelegate; properties: "z,visible" }
1434  PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1435  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
1436  UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1437  UbuntuNumberAnimation { target: windowInfoItem; properties: "opacity"; duration: priv.animationDuration }
1438  },
1439  Transition {
1440  from: "normal,staged"; to: "stagedWithSideStage"
1441  UbuntuNumberAnimation { target: appDelegate; properties: "x,y"; duration: priv.animationDuration }
1442  UbuntuNumberAnimation { target: appDelegate; properties: "requestedWidth,requestedHeight"; duration: priv.animationDuration }
1443  },
1444  Transition {
1445  to: "windowedRightEdge"
1446  ScriptAction {
1447  script: {
1448  windowedRightEdgeMaths.startX = appDelegate.requestedX
1449  windowedRightEdgeMaths.startY = appDelegate.requestedY
1450 
1451  if (index == 1) {
1452  var thisRect = { x: appDelegate.windowedX, y: appDelegate.windowedY, width: appDelegate.requestedWidth, height: appDelegate.requestedHeight }
1453  var otherDelegate = appRepeater.itemAt(0);
1454  var otherRect = { x: otherDelegate.windowedX, y: otherDelegate.windowedY, width: otherDelegate.requestedWidth, height: otherDelegate.requestedHeight }
1455  var intersectionRect = MathUtils.intersectionRect(thisRect, otherRect)
1456  var mappedInterSectionRect = appDelegate.mapFromItem(root, intersectionRect.x, intersectionRect.y)
1457  opacityEffect.maskX = mappedInterSectionRect.x
1458  opacityEffect.maskY = mappedInterSectionRect.y
1459  opacityEffect.maskWidth = intersectionRect.width
1460  opacityEffect.maskHeight = intersectionRect.height
1461  }
1462  }
1463  }
1464  },
1465  Transition {
1466  from: "stagedRightEdge"; to: "staged"
1467  enabled: rightEdgeDragArea.cancelled // only transition back to state if the gesture was cancelled, in the other cases we play the focusAnimations.
1468  SequentialAnimation {
1469  ParallelAnimation {
1470  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height,width,scale"; duration: priv.animationDuration }
1471  UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1472  }
1473  // We need to release scaleToPreviewSize at last
1474  PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1475  PropertyAction { target: appDelegate; property: "visible" }
1476  }
1477  },
1478  Transition {
1479  id: windowedTransition
1480  from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
1481  to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1482  enabled: appDelegate.animationsEnabled
1483  SequentialAnimation {
1484  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1485  UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,opacity,scale,requestedWidth,requestedHeight,windowedWidth,windowedHeight";
1486  duration: priv.animationDuration }
1487  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1488  ScriptAction { script: { fakeRectangle.stop(); } }
1489  }
1490  }
1491  ]
1492 
1493  Binding {
1494  target: PanelState
1495  property: "buttonsAlwaysVisible"
1496  value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1497  }
1498 
1499  WindowResizeArea {
1500  id: resizeArea
1501  objectName: "windowResizeArea"
1502 
1503  anchors.fill: appDelegate
1504 
1505  // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1506  anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1507 
1508  target: appDelegate
1509  minimumY: PanelState.panelHeight // disallow resizing up past Panel
1510  minWidth: units.gu(10)
1511  minHeight: units.gu(10)
1512  borderThickness: units.gu(2)
1513  enabled: false
1514  visible: enabled
1515 
1516  onPressed: {
1517  appDelegate.activate();
1518  }
1519  }
1520 
1521  DecoratedWindow {
1522  id: decoratedWindow
1523  objectName: "decoratedWindow"
1524  anchors.left: appDelegate.left
1525  anchors.top: appDelegate.top
1526  application: model.application
1527  surface: model.window.surface
1528  active: appDelegate.focus
1529  focus: true
1530  interactive: root.interactive
1531  showDecoration: 1
1532  maximizeButtonShown: appDelegate.canBeMaximized
1533  overlayShown: touchControls.overlayShown
1534  width: implicitWidth
1535  height: implicitHeight
1536  highlightSize: windowInfoItem.iconMargin / 2
1537  stageWidth: appContainer.width
1538  stageHeight: appContainer.height
1539 
1540  requestedWidth: appDelegate.requestedWidth
1541  requestedHeight: appDelegate.requestedHeight
1542 
1543  property int oldRequestedWidth: -1
1544  property int oldRequestedHeight: -1
1545 
1546  onRequestedWidthChanged: oldRequestedWidth = requestedWidth
1547  onRequestedHeightChanged: oldRequestedHeight = requestedHeight
1548 
1549  onCloseClicked: { appDelegate.close(); }
1550  onMaximizeClicked: {
1551  if (appDelegate.canBeMaximized) {
1552  appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
1553  }
1554  }
1555  onMaximizeHorizontallyClicked: {
1556  if (appDelegate.canBeMaximizedHorizontally) {
1557  appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
1558  }
1559  }
1560  onMaximizeVerticallyClicked: {
1561  if (appDelegate.canBeMaximizedVertically) {
1562  appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
1563  }
1564  }
1565  onMinimizeClicked: { appDelegate.requestMinimize(); }
1566  onDecorationPressed: { appDelegate.activate(); }
1567  onDecorationReleased: fakeRectangle.commit();
1568 
1569  property real angle: 0
1570  property real itemScale: 1
1571  transform: [
1572  Scale {
1573  origin.x: 0
1574  origin.y: decoratedWindow.implicitHeight / 2
1575  xScale: decoratedWindow.itemScale
1576  yScale: decoratedWindow.itemScale
1577  },
1578  Rotation {
1579  origin { x: 0; y: (decoratedWindow.height / 2) }
1580  axis { x: 0; y: 1; z: 0 }
1581  angle: decoratedWindow.angle
1582  }
1583  ]
1584  }
1585 
1586  OpacityMask {
1587  id: opacityEffect
1588  anchors.fill: decoratedWindow
1589  }
1590 
1591  WindowControlsOverlay {
1592  id: touchControls
1593  target: appDelegate
1594  enabled: false
1595  visible: enabled
1596  stageWidth: appContainer.width
1597  stageHeight: appContainer.height
1598 
1599  onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
1600  onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
1601  onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
1602  onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
1603  onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
1604  onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
1605  onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
1606  onStopFakeAnimation: fakeRectangle.stop();
1607  onDragReleased: fakeRectangle.commit();
1608  }
1609 
1610  WindowedFullscreenPolicy {
1611  id: windowedFullscreenPolicy
1612  active: root.mode == "windowed"
1613  surface: model.window.surface
1614  }
1615  StagedFullscreenPolicy {
1616  id: stagedFullscreenPolicy
1617  active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1618  surface: model.window.surface
1619  }
1620 
1621  SpreadDelegateInputArea {
1622  id: dragArea
1623  objectName: "dragArea"
1624  anchors.fill: decoratedWindow
1625  enabled: false
1626  closeable: !appDelegate.isDash
1627 
1628  onClicked: {
1629  spreadItem.highlightedIndex = index;
1630  if (distance == 0) {
1631  model.window.activate();
1632  priv.goneToSpread = false;
1633  }
1634  }
1635  onClose: {
1636  priv.closingIndex = index
1637  model.window.close();
1638  }
1639  }
1640 
1641  WindowInfoItem {
1642  id: windowInfoItem
1643  objectName: "windowInfoItem"
1644  anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(1) }
1645  title: model.application.name
1646  iconSource: model.application.icon
1647  height: spreadItem.appInfoHeight
1648  opacity: 0
1649  z: 1
1650  visible: opacity > 0
1651  maxWidth: {
1652  var nextApp = appRepeater.itemAt(index + 1);
1653  if (nextApp) {
1654  return Math.max(iconHeight, nextApp.x - appDelegate.x - units.gu(1))
1655  }
1656  return appDelegate.width;
1657  }
1658 
1659  onClicked: {
1660  spreadItem.highlightedIndex = index;
1661  priv.goneToSpread = false;
1662  }
1663  }
1664 
1665  MouseArea {
1666  id: closeMouseArea
1667  objectName: "closeMouseArea"
1668  anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1669  readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1670  visible: !appDelegate.isDash && dragArea.distance == 0
1671  && index == spreadItem.highlightedIndex
1672  && mousePos.y < (decoratedWindow.height / 3)
1673  && mousePos.y > -units.gu(4)
1674  && mousePos.x > -units.gu(4)
1675  && mousePos.x < (decoratedWindow.width * 2 / 3)
1676  height: units.gu(6)
1677  width: height
1678 
1679  onClicked: {
1680  priv.closingIndex = index;
1681  appDelegate.close();
1682  }
1683  Image {
1684  id: closeImage
1685  source: "graphics/window-close.svg"
1686  anchors.fill: closeMouseArea
1687  anchors.margins: units.gu(2)
1688  sourceSize.width: width
1689  sourceSize.height: height
1690  }
1691  }
1692  }
1693  }
1694  }
1695 
1696  FakeMaximizeDelegate {
1697  id: fakeRectangle
1698  target: priv.focusedAppDelegate
1699  leftMargin: root.leftMargin
1700  appContainerWidth: appContainer.width
1701  appContainerHeight: appContainer.height
1702  }
1703 
1704  EdgeBarrier {
1705  id: edgeBarrier
1706 
1707  // NB: it does its own positioning according to the specified edge
1708  edge: Qt.RightEdge
1709 
1710  onPassed: priv.goneToSpread = true;
1711 
1712  material: Component {
1713  Item {
1714  Rectangle {
1715  width: parent.height
1716  height: parent.width
1717  rotation: 90
1718  anchors.centerIn: parent
1719  gradient: Gradient {
1720  GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
1721  GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
1722  }
1723  }
1724  }
1725  }
1726  }
1727 
1728  MouseArea {
1729  id: hoverMouseArea
1730  objectName: "hoverMouseArea"
1731  anchors.fill: appContainer
1732  propagateComposedEvents: true
1733  hoverEnabled: true
1734  enabled: false
1735  visible: enabled
1736 
1737  property int scrollAreaWidth: width / 3
1738  property bool progressiveScrollingEnabled: false
1739 
1740  onMouseXChanged: {
1741  mouse.accepted = false
1742 
1743  if (hoverMouseArea.pressed) {
1744  return;
1745  }
1746 
1747  // Find the hovered item and mark it active
1748  for (var i = appRepeater.count - 1; i >= 0; i--) {
1749  var appDelegate = appRepeater.itemAt(i);
1750  var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1751  var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1752  if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1753  spreadItem.highlightedIndex = i;
1754  break;
1755  }
1756  }
1757 
1758  if (floatingFlickable.contentWidth > floatingFlickable.width) {
1759  var margins = floatingFlickable.width * 0.05;
1760 
1761  if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1762  progressiveScrollingEnabled = true
1763  }
1764 
1765  // do we need to scroll?
1766  if (mouseX < scrollAreaWidth + margins) {
1767  var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1768  var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1769  floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1770  }
1771  if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1772  var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1773  var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1774  floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1775  }
1776  }
1777  }
1778 
1779  onPressed: mouse.accepted = false
1780  }
1781 
1782  FloatingFlickable {
1783  id: floatingFlickable
1784  objectName: "spreadFlickable"
1785  anchors.fill: appContainer
1786  enabled: false
1787  contentWidth: spreadItem.spreadTotalWidth
1788 
1789  function snap(toIndex) {
1790  var delegate = appRepeater.itemAt(toIndex)
1791  var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1792  if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1793  var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1794  snapAnimation.to = floatingFlickable.contentX - offset;
1795  snapAnimation.start();
1796  } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1797  var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1798  snapAnimation.to = floatingFlickable.contentX - offset;
1799  snapAnimation.start();
1800  }
1801  }
1802  UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1803  }
1804 
1805  PropertyAnimation {
1806  id: shortRightEdgeSwipeAnimation
1807  property: "x"
1808  to: 0
1809  duration: priv.animationDuration
1810  }
1811 
1812  SwipeArea {
1813  id: rightEdgeDragArea
1814  objectName: "rightEdgeDragArea"
1815  direction: Direction.Leftwards
1816  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1817  width: root.dragAreaWidth
1818  enabled: root.spreadEnabled
1819 
1820  property var gesturePoints: []
1821  property bool cancelled: false
1822 
1823  property real progress: -touchPosition.x / root.width
1824  onProgressChanged: {
1825  if (dragging) {
1826  draggedProgress = progress;
1827  }
1828  }
1829 
1830  property real draggedProgress: 0
1831 
1832  onTouchPositionChanged: {
1833  gesturePoints.push(touchPosition.x);
1834  if (gesturePoints.length > 10) {
1835  gesturePoints.splice(0, gesturePoints.length - 10)
1836  }
1837  }
1838 
1839  onDraggingChanged: {
1840  if (dragging) {
1841  // A potential edge-drag gesture has started. Start recording it
1842  gesturePoints = [];
1843  cancelled = false;
1844  draggedProgress = 0;
1845  } else {
1846  // Ok. The user released. Did he drag far enough to go to full spread?
1847  if (gesturePoints[gesturePoints.length - 1] < -spreadItem.rightEdgeBreakPoint * spreadItem.width ) {
1848 
1849  // He dragged far enough, but if the last movement was a flick to the right again, he wants to cancel the spread again.
1850  var oneWayFlickToRight = true;
1851  var smallestX = gesturePoints[0]-1;
1852  for (var i = 0; i < gesturePoints.length; i++) {
1853  if (gesturePoints[i] <= smallestX) {
1854  oneWayFlickToRight = false;
1855  break;
1856  }
1857  smallestX = gesturePoints[i];
1858  }
1859 
1860  if (!oneWayFlickToRight) {
1861  // Ok, the user made it, let's go to spread!
1862  priv.goneToSpread = true;
1863  } else {
1864  cancelled = true;
1865  }
1866  } else {
1867  // Ok, the user didn't drag far enough to cross the breakPoint
1868  // Find out if it was a one-way movement to the left, in which case we just switch directly to next app.
1869  var oneWayFlick = true;
1870  var smallestX = rightEdgeDragArea.width;
1871  for (var i = 0; i < gesturePoints.length; i++) {
1872  if (gesturePoints[i] >= smallestX) {
1873  oneWayFlick = false;
1874  break;
1875  }
1876  smallestX = gesturePoints[i];
1877  }
1878 
1879  if (appRepeater.count > 1 &&
1880  (oneWayFlick && rightEdgeDragArea.distance > units.gu(2) || rightEdgeDragArea.distance > spreadItem.rightEdgeBreakPoint * spreadItem.width)) {
1881  var nextStage = appRepeater.itemAt(priv.nextInStack).stage
1882  for (var i = 0; i < appRepeater.count; i++) {
1883  if (i != priv.nextInStack && appRepeater.itemAt(i).stage == nextStage) {
1884  appRepeater.itemAt(i).playHidingAnimation()
1885  break;
1886  }
1887  }
1888  appRepeater.itemAt(priv.nextInStack).playFocusAnimation()
1889  if (appRepeater.itemAt(priv.nextInStack).stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1890  sideStage.show();
1891  }
1892 
1893  } else {
1894  cancelled = true;
1895  }
1896 
1897  gesturePoints = [];
1898  }
1899  }
1900  }
1901  }
1902 
1903  TabletSideStageTouchGesture {
1904  id: triGestureArea
1905  objectName: "triGestureArea"
1906  anchors.fill: parent
1907  enabled: false
1908  property Item appDelegate
1909 
1910  dragComponent: dragComponent
1911  dragComponentProperties: { "appDelegate": appDelegate }
1912 
1913  onPressed: {
1914  function matchDelegate(obj) { return String(obj.objectName).indexOf("appDelegate") >= 0; }
1915 
1916  var delegateAtCenter = Functions.itemAt(appContainer, x, y, matchDelegate);
1917  if (!delegateAtCenter) return;
1918 
1919  appDelegate = delegateAtCenter;
1920  }
1921 
1922  onClicked: {
1923  if (sideStage.shown) {
1924  sideStage.hide();
1925  } else {
1926  sideStage.show();
1927  priv.updateMainAndSideStageIndexes()
1928  }
1929  }
1930 
1931  onDragStarted: {
1932  // If we're dragging to the sidestage.
1933  if (!sideStage.shown) {
1934  sideStage.show();
1935  }
1936  }
1937 
1938  Component {
1939  id: dragComponent
1940  SurfaceContainer {
1941  property Item appDelegate
1942 
1943  surface: appDelegate ? appDelegate.surface : null
1944 
1945  consumesInput: false
1946  interactive: false
1947  focus: false
1948  requestedWidth: appDelegate.requestedWidth
1949  requestedHeight: appDelegate.requestedHeight
1950 
1951  width: units.gu(40)
1952  height: units.gu(40)
1953 
1954  Drag.hotSpot.x: width/2
1955  Drag.hotSpot.y: height/2
1956  // only accept opposite stage.
1957  Drag.keys: {
1958  if (!surface) return "Disabled";
1959  if (appDelegate.isDash) return "Disabled";
1960 
1961  if (appDelegate.stage === ApplicationInfo.MainStage) {
1962  if (appDelegate.application.supportedOrientations
1963  & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1964  return "MainStage";
1965  }
1966  return "Disabled";
1967  }
1968  return "SideStage";
1969  }
1970  }
1971  }
1972  }
1973 }