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