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