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