Unity 8
DesktopStage.qml
1 /*
2  * Copyright (C) 2014-2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import Ubuntu.Components 1.3
19 import Unity.Application 0.1
20 import "../Components/PanelState"
21 import "../Components"
22 import Utils 0.1
23 import Ubuntu.Gestures 0.1
24 import GlobalShortcut 1.0
25 
26 AbstractStage {
27  id: root
28  anchors.fill: parent
29  paintBackground: false
30 
31  // functions to be called from outside
32  function updateFocusedAppOrientation() { /* TODO */ }
33  function updateFocusedAppOrientationAnimated() { /* TODO */}
34  function pushRightEdge(amount) {
35  if (spread.state === "") {
36  edgeBarrier.push(amount);
37  }
38  }
39 
40  // Used by TutorialRight
41  property bool spreadShown: spread.state == "altTab"
42 
43  mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
44 
45  // application windows never rotate independently
46  mainAppWindowOrientationAngle: shellOrientationAngle
47 
48  orientationChangesEnabled: true
49 
50  GlobalShortcut {
51  id: closeWindowShortcut
52  shortcut: Qt.AltModifier|Qt.Key_F4
53  onTriggered: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
54  active: priv.focusedAppDelegate !== null
55  }
56 
57  GlobalShortcut {
58  id: showSpreadShortcut
59  shortcut: Qt.MetaModifier|Qt.Key_W
60  onTriggered: spread.state = "altTab"
61  }
62 
63  GlobalShortcut {
64  id: minimizeAllShortcut
65  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
66  onTriggered: priv.minimizeAllWindows()
67  }
68 
69  GlobalShortcut {
70  id: maximizeWindowShortcut
71  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
72  onTriggered: priv.focusedAppDelegate.maximize()
73  active: priv.focusedAppDelegate !== null
74  }
75 
76  GlobalShortcut {
77  id: maximizeWindowLeftShortcut
78  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
79  onTriggered: priv.focusedAppDelegate.maximizeLeft()
80  active: priv.focusedAppDelegate !== null
81  }
82 
83  GlobalShortcut {
84  id: maximizeWindowRightShortcut
85  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
86  onTriggered: priv.focusedAppDelegate.maximizeRight()
87  active: priv.focusedAppDelegate !== null
88  }
89 
90  GlobalShortcut {
91  id: minimizeRestoreShortcut
92  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
93  onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
94  priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
95  ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
96  active: priv.focusedAppDelegate !== null
97  }
98 
99  GlobalShortcut {
100  shortcut: Qt.AltModifier|Qt.Key_Print
101  onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
102  active: priv.focusedAppDelegate !== null
103  }
104 
105  Connections {
106  target: root.topLevelSurfaceList
107  onCountChanged: {
108  if (spread.state == "altTab") {
109  spread.cancel();
110  }
111  }
112  }
113 
114  QtObject {
115  id: priv
116  objectName: "DesktopStagePrivate"
117 
118  property var focusedAppDelegate: null
119  onFocusedAppDelegateChanged: {
120  if (spread.state == "altTab") {
121  spread.state = "";
122  }
123  }
124 
125  property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
126 
127  function updateForegroundMaximizedApp() {
128  var found = false;
129  for (var i = 0; i < appRepeater.count && !found; i++) {
130  var item = appRepeater.itemAt(i);
131  if (item && item.visuallyMaximized) {
132  foregroundMaximizedAppDelegate = item;
133  found = true;
134  }
135  }
136  if (!found) {
137  foregroundMaximizedAppDelegate = null;
138  }
139  }
140 
141  function minimizeAllWindows() {
142  for (var i = 0; i < appRepeater.count; i++) {
143  var appDelegate = appRepeater.itemAt(i);
144  if (appDelegate && !appDelegate.minimized) {
145  appDelegate.minimize();
146  }
147  }
148  }
149 
150  function focusNext() {
151  for (var i = 0; i < appRepeater.count; i++) {
152  var appDelegate = appRepeater.itemAt(i);
153  if (appDelegate && !appDelegate.minimized) {
154  appDelegate.focus = true;
155  return;
156  }
157  }
158  }
159  }
160 
161  Connections {
162  target: PanelState
163  onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
164  onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
165  onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
166  onFocusMaximizedApp: {
167  if (priv.foregroundMaximizedAppDelegate) {
168  priv.foregroundMaximizedAppDelegate.focus = true;
169  }
170  }
171  }
172 
173  Binding {
174  target: PanelState
175  property: "buttonsVisible"
176  value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
177  && spread.state == ""
178  }
179 
180  Binding {
181  target: PanelState
182  property: "title"
183  value: {
184  if (priv.focusedAppDelegate !== null && spread.state == "") {
185  if (priv.focusedAppDelegate.maximized)
186  return priv.focusedAppDelegate.title
187  else
188  return priv.focusedAppDelegate.appName
189  }
190  return ""
191  }
192  when: priv.focusedAppDelegate
193  }
194 
195  Binding {
196  target: PanelState
197  property: "dropShadow"
198  value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null
199  }
200 
201  Binding {
202  target: PanelState
203  property: "closeButtonShown"
204  value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && priv.focusedAppDelegate.application.appId !== "unity8-dash"
205  }
206 
207  Component.onDestruction: {
208  PanelState.title = "";
209  PanelState.buttonsVisible = false;
210  PanelState.dropShadow = false;
211  }
212 
213  Instantiator {
214  model: root.applicationManager
215  delegate: Binding {
216  target: model.application
217  property: "requestedState"
218 
219  // TODO: figure out some lifecycle policy, like suspending minimized apps
220  // if running on a tablet or something.
221  // TODO: If the device has a dozen suspended apps because it was running
222  // in staged mode, when it switches to Windowed mode it will suddenly
223  // resume all those apps at once. We might want to avoid that.
224  value: ApplicationInfoInterface.RequestedRunning // Always running for now
225  }
226  }
227 
228  Binding {
229  target: MirFocusController
230  property: "focusedSurface"
231  value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
232  when: !appRepeater.startingUp && root.parent
233  }
234 
235  FocusScope {
236  id: appContainer
237  objectName: "appContainer"
238  anchors.fill: parent
239  focus: spread.state !== "altTab"
240 
241  CrossFadeImage {
242  id: wallpaper
243  anchors.fill: parent
244  source: root.background
245  sourceSize { height: root.height; width: root.width }
246  fillMode: Image.PreserveAspectCrop
247  }
248 
249  TopLevelSurfaceRepeater {
250  id: appRepeater
251  model: topLevelSurfaceList
252  objectName: "appRepeater"
253 
254  delegate: FocusScope {
255  id: appDelegate
256  objectName: "appDelegate_" + model.id
257  // z might be overriden in some cases by effects, but we need z ordering
258  // to calculate occlusion detection
259  property int normalZ: topLevelSurfaceList.count - index
260  onNormalZChanged: {
261  if (visuallyMaximized) {
262  priv.updateForegroundMaximizedApp();
263  }
264  }
265  z: normalZ
266  x: priv.focusedAppDelegate ? priv.focusedAppDelegate.x + units.gu(3) : (normalZ - 1) * units.gu(3)
267  y: priv.focusedAppDelegate ? priv.focusedAppDelegate.y + units.gu(3) : normalZ * units.gu(3)
268 
269  width: decoratedWindow.width
270  height: decoratedWindow.height
271 
272  Connections {
273  target: root
274  onShellOrientationAngleChanged: {
275  // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
276  if (application && application.rotatesWindowContents) {
277  if (state == "normal") {
278  var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
279  angleDiff = (360 + angleDiff) % 360;
280  if (angleDiff === 90 || angleDiff === 270) {
281  var aux = decoratedWindow.requestedHeight;
282  decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.visibleDecorationHeight;
283  decoratedWindow.requestedWidth = aux - decoratedWindow.visibleDecorationHeight;
284  }
285  }
286  decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
287  } else {
288  decoratedWindow.surfaceOrientationAngle = 0;
289  }
290  }
291  }
292 
293  readonly property alias application: decoratedWindow.application
294  readonly property alias minimumWidth: decoratedWindow.minimumWidth
295  readonly property alias minimumHeight: decoratedWindow.minimumHeight
296  readonly property alias maximumWidth: decoratedWindow.maximumWidth
297  readonly property alias maximumHeight: decoratedWindow.maximumHeight
298  readonly property alias widthIncrement: decoratedWindow.widthIncrement
299  readonly property alias heightIncrement: decoratedWindow.heightIncrement
300  property int requestedWidth: -1
301  property int requestedHeight: -1
302 
303  readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
304  readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
305  readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
306  readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
307  readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
308  readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
309  readonly property alias fullscreen: decoratedWindow.fullscreen
310 
311  property int windowState: WindowStateStorage.WindowStateNormal
312  property bool animationsEnabled: true
313  property alias title: decoratedWindow.title
314  readonly property string appName: model.application ? model.application.name : ""
315  property bool visuallyMaximized: false
316  property bool visuallyMinimized: false
317 
318  readonly property var surface: model.surface
319  readonly property alias resizeArea: resizeArea
320 
321  function claimFocus() {
322  if (spread.state == "altTab") {
323  spread.cancel();
324  }
325  appDelegate.restore();
326  }
327  Connections {
328  target: model.surface
329  onFocusRequested: claimFocus();
330  }
331  Connections {
332  target: model.application
333  onFocusRequested: {
334  if (!model.surface) {
335  // when an app has no surfaces, we assume there's only one entry representing it:
336  // this delegate.
337  claimFocus();
338  } else {
339  // if the application has surfaces, focus request should be at surface-level.
340  }
341  }
342  }
343 
344  onFocusChanged: {
345  if (appRepeater.startingUp)
346  return;
347 
348  if (focus) {
349  // If we're orphan (!parent) it means this stage is no longer the current one
350  // and will be deleted shortly. So we should no longer have a say over the model
351  if (root.parent) {
352  topLevelSurfaceList.raiseId(model.id);
353  }
354 
355  priv.focusedAppDelegate = appDelegate;
356  } else if (!focus && priv.focusedAppDelegate === appDelegate) {
357  priv.focusedAppDelegate = null;
358  // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
359  MirFocusController.focusedSurface = null;
360  }
361  }
362  Component.onCompleted: {
363  if (application && application.rotatesWindowContents) {
364  decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
365  } else {
366  decoratedWindow.surfaceOrientationAngle = 0;
367  }
368 
369  // NB: We're differentiating if this delegate was created in response to a new entry in the model
370  // or if the Repeater is just populating itself with delegates to match the model it received.
371  if (!appRepeater.startingUp) {
372  // a top level window is always the focused one when it first appears, unfocusing
373  // any preexisting one
374  focus = true;
375  }
376  }
377  Component.onDestruction: {
378  if (!root.parent) {
379  // This stage is about to be destroyed. Don't mess up with the model at this point
380  return;
381  }
382 
383  if (visuallyMaximized) {
384  priv.updateForegroundMaximizedApp();
385  }
386 
387  if (focus) {
388  // focus some other window
389  for (var i = 0; i < appRepeater.count; i++) {
390  var appDelegate = appRepeater.itemAt(i);
391  if (appDelegate && !appDelegate.minimized && i != index) {
392  appDelegate.focus = true;
393  return;
394  }
395  }
396  }
397  }
398 
399  onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
400 
401  visible: (
402  !visuallyMinimized
403  && !greeter.fullyShown
404  && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
405  )
406  || decoratedWindow.fullscreen
407  || (spread.state == "altTab" && index === spread.highlightedIndex)
408 
409  function close() {
410  model.surface.close();
411  }
412 
413  function maximize(animated) {
414  animationsEnabled = (animated === undefined) || animated;
415  windowState = WindowStateStorage.WindowStateMaximized;
416  }
417  function maximizeLeft(animated) {
418  animationsEnabled = (animated === undefined) || animated;
419  windowState = WindowStateStorage.WindowStateMaximizedLeft;
420  }
421  function maximizeRight(animated) {
422  animationsEnabled = (animated === undefined) || animated;
423  windowState = WindowStateStorage.WindowStateMaximizedRight;
424  }
425  function maximizeHorizontally(animated) {
426  animationsEnabled = (animated === undefined) || animated;
427  windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
428  }
429  function maximizeVertically(animated) {
430  animationsEnabled = (animated === undefined) || animated;
431  windowState = WindowStateStorage.WindowStateMaximizedVertically;
432  }
433  function minimize(animated) {
434  animationsEnabled = (animated === undefined) || animated;
435  windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
436  }
437  function restoreFromMaximized(animated) {
438  animationsEnabled = (animated === undefined) || animated;
439  windowState = WindowStateStorage.WindowStateNormal;
440  }
441  function restore(animated) {
442  animationsEnabled = (animated === undefined) || animated;
443  windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
444  if (maximized)
445  maximize();
446  else if (maximizedLeft)
447  maximizeLeft();
448  else if (maximizedRight)
449  maximizeRight();
450  else if (maximizedHorizontally)
451  maximizeHorizontally();
452  else if (maximizedVertically)
453  maximizeVertically();
454 
455  focus = true;
456  }
457 
458  function playFocusAnimation() {
459  focusAnimation.start()
460  }
461 
462  UbuntuNumberAnimation {
463  id: focusAnimation
464  target: appDelegate
465  property: "scale"
466  from: 0.98
467  to: 1
468  duration: UbuntuAnimation.SnapDuration
469  }
470 
471  states: [
472  State {
473  name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
474  PropertyChanges {
475  target: appDelegate;
476  x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
477  y: rotation == 0 ? -PanelState.panelHeight : (parent.height - height) / 2
478  requestedWidth: appContainer.width;
479  requestedHeight: appContainer.height;
480  }
481  },
482  State {
483  name: "normal";
484  when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
485  PropertyChanges {
486  target: appDelegate;
487  visuallyMinimized: false;
488  visuallyMaximized: false
489  }
490  },
491  State {
492  name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
493  PropertyChanges {
494  target: appDelegate;
495  x: root.leftMargin;
496  y: 0;
497  visuallyMinimized: false;
498  visuallyMaximized: true
499  }
500  PropertyChanges {
501  target: decoratedWindow
502  requestedWidth: appContainer.width - root.leftMargin;
503  requestedHeight: appContainer.height;
504  }
505  },
506  State {
507  name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
508  PropertyChanges {
509  target: appDelegate
510  x: root.leftMargin
511  y: PanelState.panelHeight
512  }
513  PropertyChanges {
514  target: decoratedWindow
515  requestedWidth: (appContainer.width - root.leftMargin)/2
516  requestedHeight: appContainer.height - PanelState.panelHeight
517  }
518  },
519  State {
520  name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
521  PropertyChanges {
522  target: appDelegate;
523  x: (appContainer.width + root.leftMargin)/2
524  y: PanelState.panelHeight
525  }
526  PropertyChanges {
527  target: decoratedWindow
528  requestedWidth: (appContainer.width - root.leftMargin)/2
529  requestedHeight: appContainer.height - PanelState.panelHeight
530  }
531  },
532  State {
533  name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
534  PropertyChanges { target: appDelegate; x: root.leftMargin }
535  PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
536  },
537  State {
538  name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
539  PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
540  PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
541  },
542  State {
543  name: "minimized"; when: appDelegate.minimized
544  PropertyChanges {
545  target: appDelegate;
546  x: -appDelegate.width / 2;
547  scale: units.gu(5) / appDelegate.width;
548  opacity: 0;
549  visuallyMinimized: true;
550  visuallyMaximized: false
551  }
552  }
553  ]
554  transitions: [
555  Transition {
556  to: "normal"
557  enabled: appDelegate.animationsEnabled
558  PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
559  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
560  UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
561  },
562  Transition {
563  to: "minimized"
564  enabled: appDelegate.animationsEnabled
565  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
566  SequentialAnimation {
567  ParallelAnimation {
568  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
569  UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
570  }
571  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
572  ScriptAction {
573  script: {
574  if (appDelegate.minimized) {
575  appDelegate.focus = false;
576  priv.focusNext();
577  }
578  }
579  }
580  }
581  },
582  Transition {
583  to: "*" //maximized and fullscreen
584  enabled: appDelegate.animationsEnabled
585  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
586  SequentialAnimation {
587  ParallelAnimation {
588  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
589  UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
590  }
591  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
592  }
593  }
594  ]
595 
596  Binding {
597  id: previewBinding
598  target: appDelegate
599  property: "z"
600  value: topLevelSurfaceList.count + 1
601  when: index == spread.highlightedIndex && spread.ready
602  }
603 
604  Binding {
605  target: PanelState
606  property: "buttonsAlwaysVisible"
607  value: appDelegate && appDelegate.maximized && touchControls.overlayShown
608  }
609 
610  WindowResizeArea {
611  id: resizeArea
612  objectName: "windowResizeArea"
613 
614  // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
615  anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
616 
617  target: appDelegate
618  minWidth: units.gu(10)
619  minHeight: units.gu(10)
620  borderThickness: units.gu(2)
621  windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
622  screenWidth: appContainer.width
623  screenHeight: appContainer.height
624  leftMargin: root.leftMargin
625 
626  onPressed: { appDelegate.focus = true; }
627 
628  Component.onCompleted: {
629  loadWindowState();
630  }
631 
632  property bool saveStateOnDestruction: true
633  Connections {
634  target: root
635  onStageAboutToBeUnloaded: {
636  resizeArea.saveWindowState();
637  resizeArea.saveStateOnDestruction = false;
638  fullscreenPolicy.active = false;
639  }
640  }
641  Component.onDestruction: {
642  if (saveStateOnDestruction) {
643  saveWindowState();
644  }
645  }
646  }
647 
648  DecoratedWindow {
649  id: decoratedWindow
650  objectName: "decoratedWindow"
651  anchors.left: appDelegate.left
652  anchors.top: appDelegate.top
653  application: model.application
654  surface: model.surface
655  active: appDelegate.focus
656  focus: true
657  overlayShown: touchControls.overlayShown
658 
659  requestedWidth: appDelegate.requestedWidth
660  requestedHeight: appDelegate.requestedHeight
661 
662  onCloseClicked: { appDelegate.close(); }
663  onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
664  || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
665  ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
666  onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
667  onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
668  onMinimizeClicked: appDelegate.minimize()
669  onDecorationPressed: { appDelegate.focus = true; }
670  }
671 
672  WindowControlsOverlay {
673  id: touchControls
674  anchors.fill: appDelegate
675  target: appDelegate
676  }
677 
678  WindowedFullscreenPolicy {
679  id: fullscreenPolicy
680  active: true
681  surface: model.surface
682  }
683  }
684  }
685  }
686 
687  EdgeBarrier {
688  id: edgeBarrier
689 
690  // NB: it does its own positioning according to the specified edge
691  edge: Qt.RightEdge
692 
693  onPassed: { spread.show(); }
694  material: Component {
695  Item {
696  Rectangle {
697  width: parent.height
698  height: parent.width
699  rotation: 90
700  anchors.centerIn: parent
701  gradient: Gradient {
702  GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
703  GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
704  }
705  }
706  }
707  }
708  }
709 
710  SwipeArea {
711  direction: Direction.Leftwards
712  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
713  width: units.gu(1)
714  onDraggingChanged: { if (dragging) { spread.show(); } }
715  }
716 
717  DesktopSpread {
718  id: spread
719  objectName: "spread"
720  anchors.fill: appContainer
721  workspace: appContainer
722  focus: state == "altTab"
723  altTabPressed: root.altTabPressed
724 
725  onPlayFocusAnimation: {
726  appRepeater.itemAt(index).playFocusAnimation();
727  }
728  }
729 }