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