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