@@ -0,0 +1,1875 @@
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+import GSettings 1.0
26+import "Spread"
27+import "Spread/MathUtils.js" as MathUtils
28+
29+FocusScope {
30+ id: root
31+ anchors.fill: parent
32+
33+ property QtObject applicationManager
34+ property QtObject topLevelSurfaceList
35+ property bool altTabPressed
36+ property url background
37+ property int dragAreaWidth
38+ property bool interactive
39+ property bool keepDashRunning: true
40+ property real nativeHeight
41+ property real nativeWidth
42+ property QtObject orientations
43+ property int shellOrientation
44+ property int shellOrientationAngle
45+ property bool spreadEnabled: true // If false, animations and right edge will be disabled
46+ property bool suspended
47+ property int leftMargin: 0
48+ property bool oskEnabled: false
49+ property rect inputMethodRect
50+
51+ // Configuration
52+ property string mode: "staged"
53+ property real leftEdgeDragProgress: 0
54+
55+ // Used by the tutorial code
56+ readonly property bool spreadShown: state == "spread"
57+ readonly property real rightEdgeDragProgress: rightEdgeDragArea.progress // How far left the stage has been dragged
58+
59+ // used by the snap windows (edge maximize) feature
60+ readonly property alias previewRectangle: fakeRectangle
61+
62+ readonly property var mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
63+
64+ // application windows never rotate independently
65+ property int mainAppWindowOrientationAngle: shellOrientationAngle
66+
67+ property bool orientationChangesEnabled: priv.focusedAppDelegate && priv.focusedAppDelegate.orientationChangesEnabled
68+
69+ property int supportedOrientations: {
70+ if (mainApp) {
71+ switch (mode) {
72+ case "staged":
73+ return mainApp.supportedOrientations;
74+ case "stagedWithSideStage":
75+ var orientations = mainApp.supportedOrientations;
76+ orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
77+ if (priv.sideStageItemId) {
78+ // If we have a sidestage app, support Portrait orientation
79+ // so that it will switch the sidestage app to mainstage on rotate to portrait
80+ orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
81+ }
82+ return orientations;
83+ }
84+ }
85+
86+ return Qt.PortraitOrientation |
87+ Qt.LandscapeOrientation |
88+ Qt.InvertedPortraitOrientation |
89+ Qt.InvertedLandscapeOrientation;
90+ }
91+
92+
93+ onAltTabPressedChanged: priv.goneToSpread = altTabPressed
94+
95+ property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.surface &&
96+ priv.focusedAppDelegate.surface.confinesMousePointer ?
97+ priv.focusedAppDelegate.clientAreaItem : null;
98+
99+ signal itemSnapshotRequested(Item item)
100+
101+ // functions to be called from outside
102+ function updateFocusedAppOrientation() { /* TODO */ }
103+ function updateFocusedAppOrientationAnimated() { /* TODO */}
104+ function pushRightEdge(amount) {
105+ edgeBarrier.push(amount);
106+ }
107+
108+ onSpreadEnabledChanged: {
109+ if (!spreadEnabled && root.state == "spread") {
110+ priv.goneToSpread = false;
111+ }
112+ }
113+
114+ GSettings {
115+ id: lifecycleExceptions
116+ schema.id: "com.canonical.qtmir"
117+ }
118+
119+ function isExemptFromLifecycle(appId) {
120+ var shortAppId = appId.split('_')[0];
121+ for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
122+ if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
123+ return true;
124+ }
125+ }
126+ return false;
127+ }
128+
129+ GlobalShortcut {
130+ id: closeFocusedShortcut
131+ shortcut: Qt.AltModifier|Qt.Key_F4
132+ onTriggered: {
133+ if (priv.focusedAppDelegate && !priv.focusedAppDelegate.isDash) {
134+ priv.focusedAppDelegate.close();
135+ }
136+ }
137+ }
138+
139+ GlobalShortcut {
140+ id: showSpreadShortcut
141+ shortcut: Qt.MetaModifier|Qt.Key_W
142+ onTriggered: priv.goneToSpread = true
143+ }
144+
145+ GlobalShortcut {
146+ id: minimizeAllShortcut
147+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
148+ onTriggered: priv.minimizeAllWindows()
149+ active: root.state == "windowed"
150+ }
151+
152+ GlobalShortcut {
153+ id: maximizeWindowShortcut
154+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
155+ onTriggered: priv.focusedAppDelegate.maximize()
156+ active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
157+ }
158+
159+ GlobalShortcut {
160+ id: maximizeWindowLeftShortcut
161+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
162+ onTriggered: priv.focusedAppDelegate.maximizeLeft()
163+ active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
164+ }
165+
166+ GlobalShortcut {
167+ id: maximizeWindowRightShortcut
168+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
169+ onTriggered: priv.focusedAppDelegate.maximizeRight()
170+ active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
171+ }
172+
173+ GlobalShortcut {
174+ id: minimizeRestoreShortcut
175+ shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
176+ onTriggered: priv.focusedAppDelegate.anyMaximized
177+ ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
178+ active: root.state == "windowed" && priv.focusedAppDelegate
179+ }
180+
181+ GlobalShortcut {
182+ shortcut: Qt.AltModifier|Qt.Key_Print
183+ onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
184+ active: priv.focusedAppDelegate !== null
185+ }
186+
187+ QtObject {
188+ id: priv
189+ objectName: "DesktopStagePrivate"
190+
191+ property var focusedAppDelegate: null
192+ property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
193+
194+ property bool goneToSpread: false
195+ property int closingIndex: -1
196+ property int animationDuration: UbuntuAnimation.FastDuration
197+
198+ function updateForegroundMaximizedApp() {
199+ var found = false;
200+ for (var i = 0; i < appRepeater.count && !found; i++) {
201+ var item = appRepeater.itemAt(i);
202+ if (item && item.visuallyMaximized) {
203+ foregroundMaximizedAppDelegate = item;
204+ found = true;
205+ }
206+ }
207+ if (!found) {
208+ foregroundMaximizedAppDelegate = null;
209+ }
210+ }
211+
212+ function minimizeAllWindows() {
213+ for (var i = 0; i < appRepeater.count; i++) {
214+ var appDelegate = appRepeater.itemAt(i);
215+ if (appDelegate && !appDelegate.minimized) {
216+ appDelegate.minimize();
217+ }
218+ }
219+ }
220+
221+ function focusNext() {
222+ for (var i = 0; i < appRepeater.count; i++) {
223+ var appDelegate = appRepeater.itemAt(i);
224+ if (appDelegate && !appDelegate.minimized) {
225+ appDelegate.focus = true;
226+ return;
227+ }
228+ }
229+ }
230+
231+ readonly property bool sideStageEnabled: root.mode === "stagedWithSideStage" &&
232+ (root.shellOrientation == Qt.LandscapeOrientation ||
233+ root.shellOrientation == Qt.InvertedLandscapeOrientation)
234+
235+ property var mainStageDelegate: null
236+ property var sideStageDelegate: null
237+ property int mainStageItemId: 0
238+ property int sideStageItemId: 0
239+ property string mainStageAppId: ""
240+ property string sideStageAppId: ""
241+
242+ onSideStageDelegateChanged: {
243+ if (!sideStageDelegate) {
244+ sideStage.hide();
245+ }
246+ }
247+
248+ function updateMainAndSideStageIndexes() {
249+ if (root.mode != "stagedWithSideStage") {
250+ priv.sideStageDelegate = null;
251+ priv.sideStageItemId = 0;
252+ priv.sideStageAppId = "";
253+ priv.mainStageDelegate = appRepeater.itemAt(0);
254+ priv.mainStageAppId = topLevelSurfaceList.idAt(0);
255+ priv.mainStageAppId = topLevelSurfaceList.applicationAt(0) ? topLevelSurfaceList.applicationAt(0).appId : ""
256+ return;
257+ }
258+
259+ var choseMainStage = false;
260+ var choseSideStage = false;
261+
262+ if (!root.topLevelSurfaceList)
263+ return;
264+
265+ for (var i = 0; i < appRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
266+ var appDelegate = appRepeater.itemAt(i);
267+ if (sideStage.shown && appDelegate.stage == ApplicationInfoInterface.SideStage
268+ && !choseSideStage) {
269+ priv.sideStageDelegate = appDelegate
270+ priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
271+ priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
272+ choseSideStage = true;
273+ } else if (!choseMainStage && appDelegate.stage == ApplicationInfoInterface.MainStage) {
274+ priv.mainStageDelegate = appDelegate;
275+ priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
276+ priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
277+ choseMainStage = true;
278+ }
279+ }
280+ if (!choseMainStage && priv.mainStageDelegate) {
281+ priv.mainStageDelegate = null;
282+ priv.mainStageItemId = 0;
283+ priv.mainStageAppId = "";
284+ }
285+ if (!choseSideStage && priv.sideStageDelegate) {
286+ priv.sideStageDelegate = null;
287+ priv.sideStageItemId = 0;
288+ priv.sideStageAppId = "";
289+ }
290+ }
291+
292+ property int nextInStack: {
293+ var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.itemIndex : -1;
294+ var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.itemIndex : -1;
295+ if (sideStageIndex == -1) {
296+ return topLevelSurfaceList.count > 1 ? 1 : -1;
297+ }
298+ if (mainStageIndex == 0 || sideStageIndex == 0) {
299+ if (mainStageIndex == 1 || sideStageIndex == 1) {
300+ return topLevelSurfaceList.count > 2 ? 2 : -1;
301+ }
302+ return 1;
303+ }
304+ return -1;
305+ }
306+
307+ readonly property real virtualKeyboardHeight: root.inputMethodRect.height
308+ }
309+
310+ Component.onCompleted: priv.updateMainAndSideStageIndexes();
311+
312+ Connections {
313+ target: PanelState
314+ onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
315+ onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
316+ onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
317+ }
318+
319+ Binding {
320+ target: PanelState
321+ property: "buttonsVisible"
322+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
323+ }
324+
325+ Binding {
326+ target: PanelState
327+ property: "title"
328+ value: {
329+ if (priv.focusedAppDelegate !== null) {
330+ if (priv.focusedAppDelegate.maximized)
331+ return priv.focusedAppDelegate.title
332+ else
333+ return priv.focusedAppDelegate.appName
334+ }
335+ return ""
336+ }
337+ when: priv.focusedAppDelegate
338+ }
339+
340+ Binding {
341+ target: PanelState
342+ property: "dropShadow"
343+ value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
344+ }
345+
346+ Binding {
347+ target: PanelState
348+ property: "closeButtonShown"
349+ value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !priv.focusedAppDelegate.isDash
350+ }
351+
352+ Component.onDestruction: {
353+ PanelState.title = "";
354+ PanelState.buttonsVisible = false;
355+ PanelState.dropShadow = false;
356+ }
357+
358+ Instantiator {
359+ model: root.applicationManager
360+ delegate: QtObject {
361+ property var stateBinding: Binding {
362+ readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
363+ target: model.application
364+ property: "requestedState"
365+
366+ // TODO: figure out some lifecycle policy, like suspending minimized apps
367+ // or something if running windowed.
368+ // TODO: If the device has a dozen suspended apps because it was running
369+ // in staged mode, when it switches to Windowed mode it will suddenly
370+ // resume all those apps at once. We might want to avoid that.
371+ value: root.mode === "windowed"
372+ || (isDash && root.keepDashRunning)
373+ || (!root.suspended && model.application && priv.focusedAppDelegate &&
374+ (priv.focusedAppDelegate.appId === model.application.appId ||
375+ priv.mainStageAppId === model.application.appId ||
376+ priv.sideStageAppId === model.application.appId))
377+ ? ApplicationInfoInterface.RequestedRunning
378+ : ApplicationInfoInterface.RequestedSuspended
379+ }
380+
381+ property var lifecycleBinding: Binding {
382+ target: model.application
383+ property: "exemptFromLifecycle"
384+ value: model.application
385+ ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
386+ : false
387+ }
388+ }
389+ }
390+
391+ Binding {
392+ target: MirFocusController
393+ property: "focusedSurface"
394+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.focusedSurface : null
395+ when: !appRepeater.startingUp && root.parent
396+ }
397+
398+ states: [
399+ State {
400+ name: "spread"; when: priv.goneToSpread
401+ PropertyChanges { target: floatingFlickable; enabled: true }
402+ PropertyChanges { target: spreadItem; focus: true }
403+ PropertyChanges { target: hoverMouseArea; enabled: true }
404+ PropertyChanges { target: rightEdgeDragArea; enabled: false }
405+ PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
406+ },
407+ State {
408+ name: "stagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "staged"
409+ },
410+ State {
411+ name: "sideStagedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "stagedWithSideStage"
412+ PropertyChanges {
413+ target: sideStage
414+ opacity: priv.sideStageDelegate.x === sideStage.x ? 1 : 0
415+ visible: true
416+ }
417+ },
418+ State {
419+ name: "windowedRightEdge"; when: (rightEdgeDragArea.dragging || edgeBarrier.progress > 0) && root.mode == "windowed"
420+ },
421+ State {
422+ name: "staged"; when: root.mode === "staged"
423+ },
424+ State {
425+ name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
426+ PropertyChanges { target: triGestureArea; enabled: true }
427+ PropertyChanges { target: sideStage; visible: true }
428+ },
429+ State {
430+ name: "windowed"; when: root.mode === "windowed"
431+ }
432+ ]
433+ transitions: [
434+ Transition {
435+ from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
436+ PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
437+ },
438+ Transition {
439+ to: "spread"
440+ PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
441+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
442+ },
443+ Transition {
444+ from: "spread"
445+ SequentialAnimation {
446+ ScriptAction {
447+ script: {
448+ var item = appRepeater.itemAt(Math.max(0, spreadItem.highlightedIndex));
449+ if (item.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
450+ sideStage.show();
451+ }
452+ item.playFocusAnimation();
453+ }
454+ }
455+ PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
456+ }
457+ },
458+ Transition {
459+ to: "stagedRightEdge"
460+ PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
461+ },
462+ Transition {
463+ to: "stagedWithSideStage"
464+ ScriptAction { script: priv.updateMainAndSideStageIndexes(); }
465+ }
466+
467+ ]
468+
469+ MouseArea {
470+ id: cancelSpreadMouseArea
471+ anchors.fill: parent
472+ enabled: false
473+ onClicked: priv.goneToSpread = false
474+ }
475+
476+ FocusScope {
477+ id: appContainer
478+ objectName: "appContainer"
479+ anchors.fill: parent
480+ focus: true
481+
482+ Wallpaper {
483+ id: wallpaper
484+ anchors.fill: parent
485+ source: root.background
486+ // Make sure it's the lowest item. Due to the left edge drag we sometimes need
487+ // to put the dash at -1 and we don't want it behind the Wallpaper
488+ z: -2
489+ }
490+
491+ Spread {
492+ id: spreadItem
493+ objectName: "spreadItem"
494+ anchors.fill: appContainer
495+ leftMargin: root.leftMargin
496+ model: root.topLevelSurfaceList
497+ spreadFlickable: floatingFlickable
498+ z: 10
499+
500+ onLeaveSpread: {
501+ priv.goneToSpread = false;
502+ }
503+ }
504+
505+ Connections {
506+ target: root.topLevelSurfaceList
507+ onListChanged: priv.updateMainAndSideStageIndexes()
508+ }
509+
510+
511+ DropArea {
512+ objectName: "MainStageDropArea"
513+ anchors {
514+ left: parent.left
515+ top: parent.top
516+ bottom: parent.bottom
517+ }
518+ width: appContainer.width - sideStage.width
519+ enabled: sideStage.enabled
520+
521+ onDropped: {
522+ drop.source.appDelegate.saveStage(ApplicationInfoInterface.MainStage);
523+ drop.source.appDelegate.focus = true;
524+ }
525+ keys: "SideStage"
526+ }
527+
528+ SideStage {
529+ id: sideStage
530+ objectName: "sideStage"
531+ shown: false
532+ height: appContainer.height
533+ x: appContainer.width - width
534+ visible: false
535+ Behavior on opacity { UbuntuNumberAnimation {} }
536+ z: {
537+ if (!priv.mainStageItemId) return 0;
538+
539+ if (priv.sideStageItemId && priv.nextInStack > 0) {
540+
541+ // Due the order in which bindings are evaluated, this might be triggered while shuffling
542+ // the list and index doesn't yet match with itemIndex (even though itemIndex: index)
543+ // Let's walk the list and compare itemIndex to make sure we have the correct one.
544+ var nextDelegateInStack = -1;
545+ for (var i = 0; i < appRepeater.count; i++) {
546+ if (appRepeater.itemAt(i).itemIndex == priv.nextInStack) {
547+ nextDelegateInStack = appRepeater.itemAt(i);
548+ break;
549+ }
550+ }
551+
552+ if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
553+ // if the next app in stack is a main stage app, put the sidestage on top of it.
554+ return 2;
555+ }
556+ return 1;
557+ }
558+
559+ return 1;
560+ }
561+
562+ onShownChanged: {
563+ if (!shown && priv.mainStageDelegate) {
564+ priv.mainStageDelegate.claimFocus();
565+ }
566+ }
567+
568+ DropArea {
569+ id: sideStageDropArea
570+ objectName: "SideStageDropArea"
571+ anchors.fill: parent
572+
573+ property bool dropAllowed: true
574+
575+ onEntered: {
576+ dropAllowed = drag.keys != "Disabled";
577+ }
578+ onExited: {
579+ dropAllowed = true;
580+ }
581+ onDropped: {
582+ if (drop.keys == "MainStage") {
583+ drop.source.appDelegate.saveStage(ApplicationInfoInterface.SideStage);
584+ drop.source.appDelegate.focus = true;
585+ }
586+ }
587+ drag {
588+ onSourceChanged: {
589+ if (!sideStageDropArea.drag.source) {
590+ dropAllowed = true;
591+ }
592+ }
593+ }
594+ }
595+ }
596+
597+ TopLevelSurfaceRepeater {
598+ id: appRepeater
599+ model: topLevelSurfaceList
600+ objectName: "appRepeater"
601+
602+ delegate: FocusScope {
603+ id: appDelegate
604+ objectName: "appDelegate_" + model.id
605+ property int itemIndex: index // We need this from outside the repeater
606+ // z might be overriden in some cases by effects, but we need z ordering
607+ // to calculate occlusion detection
608+ property int normalZ: topLevelSurfaceList.count - index
609+ onNormalZChanged: {
610+ if (visuallyMaximized) {
611+ priv.updateForegroundMaximizedApp();
612+ }
613+ }
614+ z: normalZ
615+
616+ // Normally we want x/y where we request it to be. Width/height of our delegate will
617+ // match what the actual surface size is.
618+ // Don't write to those, they will be set by states
619+ x: requestedX
620+ y: requestedY
621+ width: decoratedWindow.implicitWidth
622+ height: decoratedWindow.implicitHeight
623+
624+ // requestedX/Y/width/height is what we ask the actual surface to be.
625+ // Do not write to those, they will be set by states
626+ property real requestedX: windowedX
627+ property real requestedY: windowedY
628+ property real requestedWidth: windowedWidth
629+ property real requestedHeight: windowedHeight
630+
631+ // In those are for windowed mode. Those values basically store the window's properties
632+ // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
633+ property real windowedX
634+ property real windowedY
635+ property real windowedWidth
636+ property real windowedHeight
637+
638+ // unlike windowedX/Y, this is the last known grab position before being pushed against edges/corners
639+ // when restoring, the window should return to these, not to the place where it was dropped near the edge
640+ property real restoredX
641+ property real restoredY
642+
643+ Binding {
644+ target: appDelegate
645+ property: "y"
646+ value: appDelegate.requestedY -
647+ Math.min(appDelegate.requestedY - PanelState.panelHeight,
648+ Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
649+ when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
650+ && root.inputMethodRect.height > 0
651+
652+ }
653+
654+ Behavior on x { id: xBehavior; enabled: priv.closingIndex >= 0; UbuntuNumberAnimation { onRunningChanged: if (!running) priv.closingIndex = -1} }
655+
656+ Connections {
657+ target: root
658+ onShellOrientationAngleChanged: {
659+ // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
660+ if (application && application.rotatesWindowContents) {
661+ if (root.state == "windowed") {
662+ var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
663+ angleDiff = (360 + angleDiff) % 360;
664+ if (angleDiff === 90 || angleDiff === 270) {
665+ var aux = decoratedWindow.requestedHeight;
666+ decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.decorationHeight;
667+ decoratedWindow.requestedWidth = aux - decoratedWindow.decorationHeight;
668+ }
669+ }
670+ decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
671+ } else {
672+ decoratedWindow.surfaceOrientationAngle = 0;
673+ }
674+ }
675+ }
676+ Connections {
677+ target: priv
678+ onSideStageEnabledChanged: refreshStage()
679+ }
680+
681+ readonly property alias application: decoratedWindow.application
682+ readonly property alias minimumWidth: decoratedWindow.minimumWidth
683+ readonly property alias minimumHeight: decoratedWindow.minimumHeight
684+ readonly property alias maximumWidth: decoratedWindow.maximumWidth
685+ readonly property alias maximumHeight: decoratedWindow.maximumHeight
686+ readonly property alias widthIncrement: decoratedWindow.widthIncrement
687+ readonly property alias heightIncrement: decoratedWindow.heightIncrement
688+
689+ readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
690+ readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
691+ readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
692+ readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
693+ readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
694+ readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
695+ readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
696+ readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
697+ readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
698+ readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
699+ maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
700+
701+ readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
702+ readonly property bool fullscreen: surface ? surface.state === Mir.FullscreenState : application.fullscreen
703+
704+ readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
705+ readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
706+ (maximumHeight == 0 || maximumHeight >= appContainer.height)
707+ readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
708+ (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
709+ readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
710+ readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
711+ readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
712+
713+ property int windowState: WindowStateStorage.WindowStateNormal
714+ property bool animationsEnabled: true
715+ property alias title: decoratedWindow.title
716+ readonly property string appName: model.application ? model.application.name : ""
717+ property bool visuallyMaximized: false
718+ property bool visuallyMinimized: false
719+
720+ property int stage: ApplicationInfoInterface.MainStage
721+ function saveStage(newStage) {
722+ appDelegate.stage = newStage;
723+ WindowStateStorage.saveStage(appId, newStage);
724+ priv.updateMainAndSideStageIndexes()
725+ }
726+
727+ readonly property var surface: model.surface
728+ readonly property alias resizeArea: resizeArea
729+ readonly property alias focusedSurface: decoratedWindow.focusedSurface
730+ readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
731+
732+ readonly property string appId: model.application.appId
733+ readonly property bool isDash: appId == "unity8-dash"
734+ readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
735+
736+ function claimFocus() {
737+ if (root.state == "spread") {
738+ spreadItem.highlightedIndex = index
739+ priv.goneToSpread = false;
740+ }
741+ if (root.mode == "stagedWithSideStage") {
742+ if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
743+ sideStage.show();
744+ }
745+ priv.updateMainAndSideStageIndexes();
746+ }
747+
748+ if (root.mode == "windowed") {
749+ appDelegate.restore(true /* animated */, appDelegate.windowState);
750+ } else {
751+ appDelegate.focus = true;
752+ }
753+ }
754+ Connections {
755+ target: model.surface
756+ onFocusRequested: {
757+ claimFocus();
758+ }
759+ }
760+ Connections {
761+ target: model.application
762+ onFocusRequested: {
763+ if (!model.surface) {
764+ // when an app has no surfaces, we assume there's only one entry representing it:
765+ // this delegate.
766+ claimFocus();
767+ } else {
768+ // if the application has surfaces, focus request should be at surface-level.
769+ }
770+ }
771+ }
772+
773+ onFocusChanged: {
774+ if (appRepeater.startingUp)
775+ return;
776+
777+ if (focus) {
778+ topLevelSurfaceList.raiseId(model.id);
779+ priv.focusedAppDelegate = appDelegate;
780+ } else if (!focus && priv.focusedAppDelegate === appDelegate && root.state != "spread") {
781+ priv.focusedAppDelegate = null;
782+ // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
783+ MirFocusController.focusedSurface = null;
784+ }
785+ }
786+ Component.onCompleted: {
787+ if (application && application.rotatesWindowContents) {
788+ decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
789+ } else {
790+ decoratedWindow.surfaceOrientationAngle = 0;
791+ }
792+
793+ // First, cascade the newly created window, relative to the currently/old focused window.
794+ windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
795+ windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
796+ // Now load any saved state. This needs to happen *after* the cascading!
797+ resizeArea.loadWindowState();
798+
799+ // NB: We're differentiating if this delegate was created in response to a new entry in the model
800+ // or if the Repeater is just populating itself with delegates to match the model it received.
801+ if (!appRepeater.startingUp) {
802+ // a top level window is always the focused one when it first appears, unfocusing
803+ // any preexisting one
804+ if (root.state == "spread") {
805+ spreadItem.highlightedIndex = index;
806+ }
807+ claimFocus();
808+ }
809+
810+ refreshStage();
811+ _constructing = false;
812+ }
813+ Component.onDestruction: {
814+ if (!root.parent) {
815+ // This stage is about to be destroyed. Don't mess up with the model at this point
816+ return;
817+ }
818+
819+ if (visuallyMaximized) {
820+ priv.updateForegroundMaximizedApp();
821+ }
822+
823+ if (focus) {
824+ // focus some other window
825+ for (var i = 0; i < appRepeater.count; i++) {
826+ var appDelegate = appRepeater.itemAt(i);
827+ if (appDelegate && !appDelegate.minimized && i != index) {
828+ appDelegate.focus = true;
829+ return;
830+ }
831+ }
832+ }
833+ }
834+
835+ onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
836+
837+ property bool _constructing: true;
838+ onStageChanged: {
839+ if (!_constructing) {
840+ priv.updateMainAndSideStageIndexes();
841+ }
842+ }
843+
844+ visible: (
845+ !visuallyMinimized
846+ && !greeter.fullyShown
847+ && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
848+ )
849+ || appDelegate.fullscreen
850+ || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
851+
852+ function close() {
853+ model.surface.close();
854+ }
855+
856+ function maximize(animated) {
857+ animationsEnabled = (animated === undefined) || animated;
858+ windowState = WindowStateStorage.WindowStateMaximized;
859+ }
860+ function maximizeLeft(animated) {
861+ animationsEnabled = (animated === undefined) || animated;
862+ windowState = WindowStateStorage.WindowStateMaximizedLeft;
863+ }
864+ function maximizeRight(animated) {
865+ animationsEnabled = (animated === undefined) || animated;
866+ windowState = WindowStateStorage.WindowStateMaximizedRight;
867+ }
868+ function maximizeHorizontally(animated) {
869+ animationsEnabled = (animated === undefined) || animated;
870+ windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
871+ }
872+ function maximizeVertically(animated) {
873+ animationsEnabled = (animated === undefined) || animated;
874+ windowState = WindowStateStorage.WindowStateMaximizedVertically;
875+ }
876+ function maximizeTopLeft(animated) {
877+ animationsEnabled = (animated === undefined) || animated;
878+ windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
879+ }
880+ function maximizeTopRight(animated) {
881+ animationsEnabled = (animated === undefined) || animated;
882+ windowState = WindowStateStorage.WindowStateMaximizedTopRight;
883+ }
884+ function maximizeBottomLeft(animated) {
885+ animationsEnabled = (animated === undefined) || animated;
886+ windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
887+ }
888+ function maximizeBottomRight(animated) {
889+ animationsEnabled = (animated === undefined) || animated;
890+ windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
891+ }
892+ function minimize(animated) {
893+ animationsEnabled = (animated === undefined) || animated;
894+ windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
895+ }
896+ function restoreFromMaximized(animated) {
897+ animationsEnabled = (animated === undefined) || animated;
898+ windowState = WindowStateStorage.WindowStateRestored;
899+ }
900+ function restore(animated,state) {
901+ animationsEnabled = (animated === undefined) || animated;
902+ windowState = state || WindowStateStorage.WindowStateRestored;
903+ windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
904+ focus = true;
905+ }
906+
907+ function playFocusAnimation() {
908+ if (state == "stagedRightEdge") {
909+ // TODO: Can we drop this if and find something that always works?
910+ if (root.mode == "staged") {
911+ rightEdgeFocusAnimation.targetX = 0
912+ rightEdgeFocusAnimation.start()
913+ } else if (root.mode == "stagedWithSideStage") {
914+ rightEdgeFocusAnimation.targetX = appDelegate.stage == ApplicationInfoInterface.SideStage ? sideStage.x : 0
915+ rightEdgeFocusAnimation.start()
916+ }
917+ } else if (state == "windowedRightEdge" || state == "windowed") {
918+ claimFocus();
919+ } else {
920+ focusAnimation.start()
921+ }
922+ }
923+ function playHidingAnimation() {
924+ if (state != "windowedRightEdge") {
925+ hidingAnimation.start()
926+ }
927+ }
928+
929+ function refreshStage() {
930+ var newStage = ApplicationInfoInterface.MainStage;
931+ if (priv.sideStageEnabled) { // we're in lanscape rotation.
932+ if (!isDash && application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
933+ var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
934+ if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
935+ // if it supports lanscape, it defaults to mainstage.
936+ defaultStage = ApplicationInfoInterface.MainStage;
937+ }
938+ newStage = WindowStateStorage.getStage(application.appId, defaultStage);
939+ }
940+ }
941+
942+ stage = newStage;
943+ if (focus && stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
944+ sideStage.show();
945+ }
946+ }
947+
948+ UbuntuNumberAnimation {
949+ id: focusAnimation
950+ target: appDelegate
951+ property: "scale"
952+ from: 0.98
953+ to: 1
954+ duration: UbuntuAnimation.SnapDuration
955+ onStarted: {
956+ topLevelSurfaceList.raiseId(model.id);
957+ }
958+ onStopped: {
959+ appDelegate.claimFocus();
960+ }
961+ }
962+ ParallelAnimation {
963+ id: rightEdgeFocusAnimation
964+ property int targetX: 0
965+ UbuntuNumberAnimation { target: appDelegate; properties: "x"; to: rightEdgeFocusAnimation.targetX; duration: priv.animationDuration }
966+ UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
967+ UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
968+ onStopped: {
969+ appDelegate.focus = true
970+ }
971+ }
972+ ParallelAnimation {
973+ id: hidingAnimation
974+ UbuntuNumberAnimation { target: appDelegate; property: "opacity"; to: 0; duration: priv.animationDuration }
975+ onStopped: appDelegate.opacity = 1
976+ }
977+
978+ SpreadMaths {
979+ id: spreadMaths
980+ spread: spreadItem
981+ itemIndex: index
982+ flickable: floatingFlickable
983+ }
984+ StageMaths {
985+ id: stageMaths
986+ sceneWidth: root.width
987+ stage: appDelegate.stage
988+ thisDelegate: appDelegate
989+ mainStageDelegate: priv.mainStageDelegate
990+ sideStageDelegate: priv.sideStageDelegate
991+ sideStageWidth: sideStage.panelWidth
992+ sideStageX: sideStage.x
993+ itemIndex: appDelegate.itemIndex
994+ nextInStack: priv.nextInStack
995+ leftEdgeDragProgress: root.leftEdgeDragProgress
996+ }
997+
998+ StagedRightEdgeMaths {
999+ id: stagedRightEdgeMaths
1000+ sceneWidth: appContainer.width - root.leftMargin
1001+ sceneHeight: appContainer.height
1002+ isMainStageApp: priv.mainStageDelegate == appDelegate
1003+ isSideStageApp: priv.sideStageDelegate == appDelegate
1004+ sideStageWidth: sideStage.width
1005+ sideStageOpen: sideStage.shown
1006+ itemIndex: index
1007+ nextInStack: priv.nextInStack
1008+ progress: 0
1009+ targetHeight: spreadItem.stackHeight
1010+ targetX: spreadMaths.targetX
1011+ startY: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1012+ targetY: spreadMaths.targetY
1013+ targetAngle: spreadMaths.targetAngle
1014+ targetScale: spreadMaths.targetScale
1015+ shuffledZ: stageMaths.itemZ
1016+ breakPoint: spreadItem.rightEdgeBreakPoint
1017+ }
1018+
1019+ WindowedRightEdgeMaths {
1020+ id: windowedRightEdgeMaths
1021+ itemIndex: index
1022+ startWidth: appDelegate.requestedWidth
1023+ startHeight: appDelegate.requestedHeight
1024+ targetHeight: spreadItem.stackHeight
1025+ targetX: spreadMaths.targetX
1026+ targetY: spreadMaths.targetY
1027+ normalZ: appDelegate.normalZ
1028+ targetAngle: spreadMaths.targetAngle
1029+ targetScale: spreadMaths.targetScale
1030+ breakPoint: spreadItem.rightEdgeBreakPoint
1031+ }
1032+
1033+ states: [
1034+ State {
1035+ name: "spread"; when: root.state == "spread"
1036+ PropertyChanges {
1037+ target: decoratedWindow;
1038+ showDecoration: false;
1039+ angle: spreadMaths.targetAngle
1040+ itemScale: spreadMaths.targetScale
1041+ scaleToPreviewSize: spreadItem.stackHeight
1042+ scaleToPreviewProgress: 1
1043+ hasDecoration: root.mode === "windowed"
1044+ shadowOpacity: spreadMaths.shadowOpacity
1045+ showHighlight: spreadItem.highlightedIndex === index
1046+ darkening: spreadItem.highlightedIndex >= 0
1047+ anchors.topMargin: dragArea.distance
1048+ }
1049+ PropertyChanges {
1050+ target: appDelegate
1051+ x: spreadMaths.targetX
1052+ y: spreadMaths.targetY
1053+ z: index
1054+ height: spreadItem.spreadItemHeight
1055+ requestedWidth: decoratedWindow.oldRequestedWidth
1056+ requestedHeight: decoratedWindow.oldRequestedHeight
1057+ visible: spreadMaths.itemVisible
1058+ }
1059+ PropertyChanges { target: dragArea; enabled: true }
1060+ PropertyChanges { target: windowInfoItem; opacity: spreadMaths.tileInfoOpacity; visible: spreadMaths.itemVisible }
1061+ },
1062+ State {
1063+ name: "stagedRightEdge"
1064+ when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1065+ PropertyChanges {
1066+ target: stagedRightEdgeMaths
1067+ progress: Math.max(edgeBarrier.progress, rightEdgeDragArea.draggedProgress)
1068+ }
1069+ PropertyChanges {
1070+ target: appDelegate
1071+ x: stagedRightEdgeMaths.animatedX
1072+ y: stagedRightEdgeMaths.animatedY
1073+ z: stagedRightEdgeMaths.animatedZ
1074+ height: stagedRightEdgeMaths.animatedHeight
1075+ requestedWidth: decoratedWindow.oldRequestedWidth
1076+ requestedHeight: decoratedWindow.oldRequestedHeight
1077+ visible: appDelegate.x < root.width
1078+ }
1079+ PropertyChanges {
1080+ target: decoratedWindow
1081+ hasDecoration: false
1082+ angle: stagedRightEdgeMaths.animatedAngle
1083+ itemScale: stagedRightEdgeMaths.animatedScale
1084+ scaleToPreviewSize: spreadItem.stackHeight
1085+ scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1086+ shadowOpacity: .3
1087+ }
1088+ },
1089+ State {
1090+ name: "windowedRightEdge"
1091+ when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || edgeBarrier.progress > 0)
1092+ PropertyChanges {
1093+ target: windowedRightEdgeMaths
1094+ progress: Math.max(rightEdgeDragArea.progress, edgeBarrier.progress)
1095+ }
1096+ PropertyChanges {
1097+ target: appDelegate
1098+ x: windowedRightEdgeMaths.animatedX
1099+ y: windowedRightEdgeMaths.animatedY
1100+ z: windowedRightEdgeMaths.animatedZ
1101+ height: stagedRightEdgeMaths.animatedHeight
1102+ requestedWidth: decoratedWindow.oldRequestedWidth
1103+ requestedHeight: decoratedWindow.oldRequestedHeight
1104+ }
1105+ PropertyChanges {
1106+ target: decoratedWindow
1107+ showDecoration: windowedRightEdgeMaths.decorationHeight
1108+ angle: windowedRightEdgeMaths.animatedAngle
1109+ itemScale: windowedRightEdgeMaths.animatedScale
1110+ scaleToPreviewSize: spreadItem.stackHeight
1111+ scaleToPreviewProgress: windowedRightEdgeMaths.scaleToPreviewProgress
1112+ shadowOpacity: .3
1113+ }
1114+ PropertyChanges {
1115+ target: opacityEffect;
1116+ opacityValue: windowedRightEdgeMaths.opacityMask
1117+ sourceItem: windowedRightEdgeMaths.opacityMask < 1 ? decoratedWindow : null
1118+ }
1119+ },
1120+ State {
1121+ name: "staged"; when: root.state == "staged"
1122+ PropertyChanges {
1123+ target: appDelegate
1124+ x: stageMaths.itemX
1125+ y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1126+ requestedWidth: appContainer.width
1127+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1128+ visuallyMaximized: true
1129+ visible: appDelegate.x < root.width
1130+ }
1131+ PropertyChanges {
1132+ target: decoratedWindow
1133+ hasDecoration: false
1134+ }
1135+ PropertyChanges {
1136+ target: resizeArea
1137+ enabled: false
1138+ }
1139+ PropertyChanges {
1140+ target: stageMaths
1141+ animateX: !focusAnimation.running && itemIndex !== spreadItem.highlightedIndex
1142+ }
1143+ },
1144+ State {
1145+ name: "stagedWithSideStage"; when: root.state == "stagedWithSideStage"
1146+ PropertyChanges {
1147+ target: stageMaths
1148+ itemIndex: index
1149+ }
1150+ PropertyChanges {
1151+ target: appDelegate
1152+ x: stageMaths.itemX
1153+ y: appDelegate.fullscreen ? 0 : PanelState.panelHeight
1154+ z: stageMaths.itemZ
1155+ requestedWidth: stageMaths.itemWidth
1156+ requestedHeight: appDelegate.fullscreen ? appContainer.height : appContainer.height - PanelState.panelHeight
1157+ visuallyMaximized: true
1158+ visible: appDelegate.x < root.width
1159+ }
1160+ PropertyChanges {
1161+ target: decoratedWindow
1162+ hasDecoration: false
1163+ }
1164+ PropertyChanges {
1165+ target: resizeArea
1166+ enabled: false
1167+ }
1168+ },
1169+ State {
1170+ name: "maximized"; when: appDelegate.windowState == WindowStateStorage.WindowStateMaximized
1171+ PropertyChanges {
1172+ target: appDelegate;
1173+ requestedX: root.leftMargin;
1174+ requestedY: 0;
1175+ visuallyMinimized: false;
1176+ visuallyMaximized: true
1177+ requestedWidth: appContainer.width - root.leftMargin;
1178+ requestedHeight: appContainer.height;
1179+ }
1180+ PropertyChanges { target: touchControls; enabled: true }
1181+ },
1182+ State {
1183+ name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
1184+ PropertyChanges {
1185+ target: appDelegate;
1186+ requestedX: 0
1187+ requestedY: 0
1188+ requestedWidth: appContainer.width;
1189+ requestedHeight: appContainer.height;
1190+ }
1191+ PropertyChanges { target: decoratedWindow; hasDecoration: false }
1192+ },
1193+ State {
1194+ name: "normal";
1195+ when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
1196+ PropertyChanges {
1197+ target: appDelegate
1198+ visuallyMinimized: false
1199+ visuallyMaximized: false
1200+ }
1201+ PropertyChanges { target: touchControls; enabled: true }
1202+ PropertyChanges { target: resizeArea; enabled: true }
1203+ PropertyChanges { target: decoratedWindow; shadowOpacity: .3}
1204+ },
1205+ State {
1206+ name: "restored";
1207+ when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1208+ extend: "normal"
1209+ PropertyChanges {
1210+ target: appDelegate;
1211+ windowedX: restoredX;
1212+ windowedY: restoredY;
1213+ }
1214+ },
1215+ State {
1216+ name: "semiMaximized"
1217+ PropertyChanges { target: touchControls; enabled: true }
1218+ PropertyChanges { target: resizeArea; enabled: true }
1219+ PropertyChanges { target: decoratedWindow; shadowOpacity: .3 }
1220+ },
1221+ State {
1222+ name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1223+ extend: "semiMaximized"
1224+ PropertyChanges {
1225+ target: appDelegate
1226+ windowedX: root.leftMargin
1227+ windowedY: PanelState.panelHeight
1228+ windowedWidth: (appContainer.width - root.leftMargin)/2
1229+ windowedHeight: appContainer.height - PanelState.panelHeight
1230+ }
1231+ },
1232+ State {
1233+ name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1234+ extend: "maximizedLeft"
1235+ PropertyChanges {
1236+ target: appDelegate;
1237+ windowedX: (appContainer.width + root.leftMargin)/2
1238+ }
1239+ },
1240+ State {
1241+ name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1242+ extend: "semiMaximized"
1243+ PropertyChanges {
1244+ target: appDelegate
1245+ windowedX: root.leftMargin
1246+ windowedY: PanelState.panelHeight
1247+ windowedWidth: (appContainer.width - root.leftMargin)/2
1248+ windowedHeight: (appContainer.height - PanelState.panelHeight)/2
1249+ }
1250+ },
1251+ State {
1252+ name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1253+ extend: "maximizedTopLeft"
1254+ PropertyChanges {
1255+ target: appDelegate
1256+ windowedX: (appContainer.width + root.leftMargin)/2
1257+ }
1258+ },
1259+ State {
1260+ name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1261+ extend: "semiMaximized"
1262+ PropertyChanges {
1263+ target: appDelegate
1264+ windowedX: root.leftMargin
1265+ windowedY: (appContainer.height + PanelState.panelHeight)/2
1266+ windowedWidth: (appContainer.width - root.leftMargin)/2
1267+ windowedHeight: appContainer.height/2
1268+ }
1269+ },
1270+ State {
1271+ name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1272+ extend: "maximizedBottomLeft"
1273+ PropertyChanges {
1274+ target: appDelegate
1275+ windowedX: (appContainer.width + root.leftMargin)/2
1276+ }
1277+ },
1278+ State {
1279+ name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1280+ extend: "semiMaximized"
1281+ PropertyChanges { target: appDelegate; requestedX: root.leftMargin; requestedY: windowedY;
1282+ requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appDelegate.windowedHeight }
1283+ },
1284+ State {
1285+ name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1286+ extend: "semiMaximized"
1287+ PropertyChanges { target: appDelegate; requestedX: windowedX; requestedY: PanelState.panelHeight;
1288+ requestedWidth: appDelegate.windowedWidth; requestedHeight: appContainer.height - PanelState.panelHeight }
1289+ },
1290+ State {
1291+ name: "minimized"; when: appDelegate.minimized
1292+ PropertyChanges {
1293+ target: appDelegate
1294+ requestedX: -appDelegate.width / 2
1295+ scale: units.gu(5) / appDelegate.width
1296+ opacity: 0;
1297+ visuallyMinimized: true
1298+ visuallyMaximized: false
1299+ }
1300+ }
1301+ ]
1302+ transitions: [
1303+ Transition {
1304+ from: "staged,stagedWithSideStage"; to: "normal"
1305+ enabled: appDelegate.animationsEnabled
1306+ PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1307+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
1308+ },
1309+ Transition {
1310+ from: "normal,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight";
1311+ to: "staged,stagedWithSideStage"
1312+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1313+ },
1314+ Transition {
1315+ from: "maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight,minimized";
1316+ to: "normal,restored"
1317+ enabled: appDelegate.animationsEnabled
1318+ PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1319+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,requestedWidth,requestedHeight,windowedWidth,windowedHeight,scale";
1320+ duration: priv.animationDuration }
1321+ },
1322+ Transition {
1323+ to: "minimized"
1324+ enabled: appDelegate.animationsEnabled
1325+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1326+ SequentialAnimation {
1327+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,opacity,scale,requestedWidth,requestedHeight" }
1328+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1329+ ScriptAction {
1330+ script: {
1331+ if (appDelegate.minimized) {
1332+ appDelegate.focus = false;
1333+ priv.focusNext();
1334+ }
1335+ }
1336+ }
1337+ }
1338+ },
1339+ Transition {
1340+ to: "spread"
1341+ // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1342+ PropertyAction { target: appDelegate; properties: "z,visible" }
1343+ PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1344+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
1345+ UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1346+ },
1347+ Transition {
1348+ from: "normal,staged"; to: "stagedWithSideStage"
1349+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y"; duration: priv.animationDuration }
1350+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedWidth,requestedHeight"; duration: priv.animationDuration }
1351+ },
1352+ Transition {
1353+ to: "windowedRightEdge"
1354+ ScriptAction {
1355+ script: {
1356+ windowedRightEdgeMaths.startX = appDelegate.requestedX
1357+ windowedRightEdgeMaths.startY = appDelegate.requestedY
1358+
1359+ if (index == 1) {
1360+ var thisRect = { x: appDelegate.windowedX, y: appDelegate.windowedY, width: appDelegate.requestedWidth, height: appDelegate.requestedHeight }
1361+ var otherDelegate = appRepeater.itemAt(0);
1362+ var otherRect = { x: otherDelegate.windowedX, y: otherDelegate.windowedY, width: otherDelegate.requestedWidth, height: otherDelegate.requestedHeight }
1363+ var intersectionRect = MathUtils.intersectionRect(thisRect, otherRect)
1364+ var mappedInterSectionRect = appDelegate.mapFromItem(root, intersectionRect.x, intersectionRect.y)
1365+ opacityEffect.maskX = mappedInterSectionRect.x
1366+ opacityEffect.maskY = mappedInterSectionRect.y
1367+ opacityEffect.maskWidth = intersectionRect.width
1368+ opacityEffect.maskHeight = intersectionRect.height
1369+ }
1370+ }
1371+ }
1372+ },
1373+ Transition {
1374+ from: "stagedRightEdge"; to: "staged"
1375+ enabled: rightEdgeDragArea.cancelled // only transition back to state if the gesture was cancelled, in the other cases we play the focusAnimations.
1376+ SequentialAnimation {
1377+ ParallelAnimation {
1378+ UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height,width,scale"; duration: priv.animationDuration }
1379+ UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1380+ }
1381+ // We need to release scaleToPreviewSize at last
1382+ PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1383+ PropertyAction { target: appDelegate; property: "visible" }
1384+ }
1385+ },
1386+ Transition {
1387+ from: "normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTop,maximizedBottom,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1388+ to: "normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTop,maximizedBottom,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1389+ enabled: appDelegate.animationsEnabled
1390+ SequentialAnimation {
1391+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1392+ UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,opacity,scale,requestedWidth,requestedHeight,windowedWidth,windowedHeight";
1393+ duration: priv.animationDuration }
1394+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1395+ ScriptAction { script: { fakeRectangle.stop(); } }
1396+ }
1397+ }
1398+ ]
1399+
1400+ Binding {
1401+ target: PanelState
1402+ property: "buttonsAlwaysVisible"
1403+ value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1404+ }
1405+
1406+ WindowResizeArea {
1407+ id: resizeArea
1408+ objectName: "windowResizeArea"
1409+
1410+ // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1411+ anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1412+
1413+ target: appDelegate
1414+ minWidth: units.gu(10)
1415+ minHeight: units.gu(10)
1416+ borderThickness: units.gu(2)
1417+ windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
1418+ screenWidth: appContainer.width
1419+ screenHeight: appContainer.height
1420+ leftMargin: root.leftMargin
1421+ enabled: false
1422+ visible: enabled
1423+
1424+ onPressed: {
1425+ appDelegate.focus = true;
1426+ }
1427+
1428+ Component.onDestruction: {
1429+ saveWindowState();
1430+ }
1431+ }
1432+
1433+ DecoratedWindow {
1434+ id: decoratedWindow
1435+ objectName: "decoratedWindow"
1436+ anchors.left: appDelegate.left
1437+ anchors.top: appDelegate.top
1438+ application: model.application
1439+ surface: model.surface
1440+ active: appDelegate.focus
1441+ focus: true
1442+ interactive: root.interactive
1443+ showDecoration: 1
1444+ maximizeButtonShown: appDelegate.canBeMaximized
1445+ overlayShown: touchControls.overlayShown
1446+ width: implicitWidth
1447+ height: implicitHeight
1448+ highlightSize: windowInfoItem.iconMargin / 2
1449+ stageWidth: appContainer.width
1450+ stageHeight: appContainer.height
1451+
1452+ requestedWidth: appDelegate.requestedWidth
1453+ requestedHeight: appDelegate.requestedHeight
1454+
1455+ property int oldRequestedWidth: -1
1456+ property int oldRequestedHeight: -1
1457+
1458+ onRequestedWidthChanged: oldRequestedWidth = requestedWidth
1459+ onRequestedHeightChanged: oldRequestedHeight = requestedHeight
1460+
1461+ onCloseClicked: { appDelegate.close(); }
1462+ onMaximizeClicked: appDelegate.anyMaximized ? appDelegate.restoreFromMaximized() : appDelegate.maximize();
1463+ onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
1464+ onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
1465+ onMinimizeClicked: appDelegate.minimize()
1466+ onDecorationPressed: { appDelegate.focus = true; }
1467+ onDecorationReleased: fakeRectangle.commit();
1468+
1469+ property real angle: 0
1470+ property real itemScale: 1
1471+ transform: [
1472+ Scale {
1473+ origin.x: 0
1474+ origin.y: decoratedWindow.implicitHeight / 2
1475+ xScale: decoratedWindow.itemScale
1476+ yScale: decoratedWindow.itemScale
1477+ },
1478+ Rotation {
1479+ origin { x: 0; y: (decoratedWindow.height / 2) }
1480+ axis { x: 0; y: 1; z: 0 }
1481+ angle: decoratedWindow.angle
1482+ }
1483+ ]
1484+ }
1485+
1486+ OpacityMask {
1487+ id: opacityEffect
1488+ anchors.fill: decoratedWindow
1489+ }
1490+
1491+ WindowControlsOverlay {
1492+ id: touchControls
1493+ target: appDelegate
1494+ enabled: false
1495+ visible: enabled
1496+ stageWidth: appContainer.width
1497+ stageHeight: appContainer.height
1498+
1499+ onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
1500+ onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
1501+ onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
1502+ onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
1503+ onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
1504+ onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
1505+ onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
1506+ onStopFakeAnimation: fakeRectangle.stop();
1507+ onDragReleased: fakeRectangle.commit();
1508+ }
1509+
1510+ WindowedFullscreenPolicy {
1511+ id: windowedFullscreenPolicy
1512+ active: root.mode == "windowed"
1513+ surface: model.surface
1514+ }
1515+ StagedFullscreenPolicy {
1516+ id: stagedFullscreenPolicy
1517+ active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1518+ surface: model.surface
1519+ }
1520+
1521+ SpreadDelegateInputArea {
1522+ id: dragArea
1523+ objectName: "dragArea"
1524+ anchors.fill: decoratedWindow
1525+ enabled: false
1526+ closeable: !appDelegate.isDash
1527+
1528+ onClicked: {
1529+ spreadItem.highlightedIndex = index;
1530+ if (distance == 0) {
1531+ priv.goneToSpread = false;
1532+ }
1533+ }
1534+ onClose: {
1535+ priv.closingIndex = index
1536+ if (model.surface) { // could be stopped by OOM
1537+ model.surface.close()
1538+ } else if (model.application) {
1539+ root.applicationManager.stopApplication(model.application.appId);
1540+ }
1541+ }
1542+ }
1543+
1544+ WindowInfoItem {
1545+ id: windowInfoItem
1546+ objectName: "windowInfoItem"
1547+ anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(1) }
1548+ title: model.application.name
1549+ iconSource: model.application.icon
1550+ height: spreadItem.appInfoHeight
1551+ opacity: 0
1552+ z: 1
1553+ visible: opacity > 0
1554+ maxWidth: {
1555+ var nextApp = appRepeater.itemAt(index + 1);
1556+ if (nextApp) {
1557+ return nextApp.x - appDelegate.x - units.gu(1)
1558+ }
1559+ return appDelegate.width;
1560+ }
1561+
1562+ onClicked: {
1563+ spreadItem.highlightedIndex = index;
1564+ priv.goneToSpread = false;
1565+ }
1566+ }
1567+
1568+ Image {
1569+ id: closeImage
1570+ anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1571+ source: "graphics/window-close.svg"
1572+ readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1573+ visible: !appDelegate.isDash && dragArea.distance == 0
1574+ && index == spreadItem.highlightedIndex
1575+ && mousePos.y < (decoratedWindow.height / 3)
1576+ && mousePos.y > -units.gu(4)
1577+ && mousePos.x > -units.gu(4)
1578+ && mousePos.x < (decoratedWindow.width * 2 / 3)
1579+ height: units.gu(2)
1580+ width: height
1581+ sourceSize.width: width
1582+ sourceSize.height: height
1583+
1584+ MouseArea {
1585+ id: closeMouseArea
1586+ objectName: "closeMouseArea"
1587+ anchors.fill: closeImage
1588+ anchors.margins: -units.gu(2)
1589+ onClicked: {
1590+ priv.closingIndex = index;
1591+ appDelegate.close();
1592+ }
1593+ }
1594+ }
1595+ }
1596+ }
1597+ }
1598+
1599+ FakeMaximizeDelegate {
1600+ id: fakeRectangle
1601+ target: priv.focusedAppDelegate
1602+ leftMargin: root.leftMargin
1603+ appContainerWidth: appContainer.width
1604+ appContainerHeight: appContainer.height
1605+ }
1606+
1607+ EdgeBarrier {
1608+ id: edgeBarrier
1609+
1610+ // NB: it does its own positioning according to the specified edge
1611+ edge: Qt.RightEdge
1612+
1613+ onPassed: priv.goneToSpread = true;
1614+
1615+ material: Component {
1616+ Item {
1617+ Rectangle {
1618+ width: parent.height
1619+ height: parent.width
1620+ rotation: 90
1621+ anchors.centerIn: parent
1622+ gradient: Gradient {
1623+ GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
1624+ GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
1625+ }
1626+ }
1627+ }
1628+ }
1629+ }
1630+
1631+ MouseArea {
1632+ id: hoverMouseArea
1633+ objectName: "hoverMouseArea"
1634+ anchors.fill: appContainer
1635+ propagateComposedEvents: true
1636+ hoverEnabled: true
1637+ enabled: false
1638+ visible: enabled
1639+
1640+ property int scrollAreaWidth: width / 3
1641+ property bool progressiveScrollingEnabled: false
1642+
1643+ onMouseXChanged: {
1644+ mouse.accepted = false
1645+
1646+ if (hoverMouseArea.pressed) {
1647+ return;
1648+ }
1649+
1650+ // Find the hovered item and mark it active
1651+ var mapped = mapToItem(appContainer, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1652+ var itemUnder = appContainer.childAt(mapped.x, mapped.y)
1653+ if (itemUnder) {
1654+ mapped = mapToItem(itemUnder, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1655+ var delegateChild = itemUnder.childAt(mapped.x, mapped.y)
1656+ if (delegateChild && (delegateChild.objectName === "dragArea" || delegateChild.objectName === "windowInfoItem")) {
1657+ spreadItem.highlightedIndex = appRepeater.indexOf(itemUnder)
1658+ }
1659+ }
1660+
1661+ if (floatingFlickable.contentWidth > floatingFlickable.width) {
1662+ var margins = floatingFlickable.width * 0.05;
1663+
1664+ if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1665+ progressiveScrollingEnabled = true
1666+ }
1667+
1668+ // do we need to scroll?
1669+ if (mouseX < scrollAreaWidth + margins) {
1670+ var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1671+ var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1672+ floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1673+ }
1674+ if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1675+ var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1676+ var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1677+ floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1678+ }
1679+ }
1680+ }
1681+
1682+ onPressed: mouse.accepted = false
1683+ }
1684+
1685+ FloatingFlickable {
1686+ id: floatingFlickable
1687+ objectName: "spreadFlickable"
1688+ anchors.fill: appContainer
1689+ enabled: false
1690+ contentWidth: spreadItem.spreadTotalWidth
1691+
1692+ function snap(toIndex) {
1693+ var delegate = appRepeater.itemAt(toIndex)
1694+ var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1695+ if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1696+ var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1697+ snapAnimation.to = floatingFlickable.contentX - offset;
1698+ snapAnimation.start();
1699+ } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1700+ var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1701+ snapAnimation.to = floatingFlickable.contentX - offset;
1702+ snapAnimation.start();
1703+ }
1704+ }
1705+ UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1706+ }
1707+
1708+ PropertyAnimation {
1709+ id: shortRightEdgeSwipeAnimation
1710+ property: "x"
1711+ to: 0
1712+ duration: priv.animationDuration
1713+ }
1714+
1715+ SwipeArea {
1716+ id: rightEdgeDragArea
1717+ objectName: "rightEdgeDragArea"
1718+ direction: Direction.Leftwards
1719+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1720+ width: root.dragAreaWidth
1721+
1722+ property var gesturePoints: []
1723+ property bool cancelled: false
1724+
1725+ property real progress: dragging ? -touchPosition.x / root.width : 0
1726+ onProgressChanged: {
1727+ if (dragging) {
1728+ draggedProgress = progress;
1729+ }
1730+ }
1731+
1732+ property real draggedProgress: 0
1733+
1734+ onTouchPositionChanged: {
1735+ gesturePoints.push(touchPosition.x);
1736+ if (gesturePoints.length > 10) {
1737+ gesturePoints.splice(0, gesturePoints.length - 10)
1738+ }
1739+ }
1740+
1741+ onDraggingChanged: {
1742+ if (dragging) {
1743+ // A potential edge-drag gesture has started. Start recording it
1744+ gesturePoints = [];
1745+ cancelled = false;
1746+ draggedProgress = 0;
1747+ } else {
1748+ // Ok. The user released. Did he drag far enough to go to full spread?
1749+ if (gesturePoints[gesturePoints.length - 1] < -spreadItem.rightEdgeBreakPoint * spreadItem.width ) {
1750+
1751+ // He dragged far enough, but if the last movement was a flick to the right again, he wants to cancel the spread again.
1752+ var oneWayFlickToRight = true;
1753+ var smallestX = gesturePoints[0]-1;
1754+ for (var i = 0; i < gesturePoints.length; i++) {
1755+ if (gesturePoints[i] <= smallestX) {
1756+ oneWayFlickToRight = false;
1757+ break;
1758+ }
1759+ smallestX = gesturePoints[i];
1760+ }
1761+
1762+ if (!oneWayFlickToRight) {
1763+ // Ok, the user made it, let's go to spread!
1764+ priv.goneToSpread = true;
1765+ } else {
1766+ cancelled = true;
1767+ }
1768+ } else {
1769+ // Ok, the user didn't drag far enough to cross the breakPoint
1770+ // Find out if it was a one-way movement to the left, in which case we just switch directly to next app.
1771+ var oneWayFlick = true;
1772+ var smallestX = rightEdgeDragArea.width;
1773+ for (var i = 0; i < gesturePoints.length; i++) {
1774+ if (gesturePoints[i] >= smallestX) {
1775+ oneWayFlick = false;
1776+ break;
1777+ }
1778+ smallestX = gesturePoints[i];
1779+ }
1780+
1781+ if (appRepeater.count > 1 &&
1782+ (oneWayFlick && rightEdgeDragArea.distance > units.gu(2) || rightEdgeDragArea.distance > spreadItem.rightEdgeBreakPoint * spreadItem.width)) {
1783+ var nextStage = appRepeater.itemAt(priv.nextInStack).stage
1784+ for (var i = 0; i < appRepeater.count; i++) {
1785+ if (i != priv.nextInStack && appRepeater.itemAt(i).stage == nextStage) {
1786+ appRepeater.itemAt(i).playHidingAnimation()
1787+ break;
1788+ }
1789+ }
1790+ appRepeater.itemAt(priv.nextInStack).playFocusAnimation()
1791+ if (appRepeater.itemAt(priv.nextInStack).stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1792+ sideStage.show();
1793+ }
1794+
1795+ } else {
1796+ cancelled = true;
1797+ }
1798+
1799+ gesturePoints = [];
1800+ }
1801+ }
1802+ }
1803+ }
1804+
1805+ TabletSideStageTouchGesture {
1806+ id: triGestureArea
1807+ objectName: "triGestureArea"
1808+ anchors.fill: parent
1809+ enabled: false
1810+ property Item appDelegate
1811+
1812+ dragComponent: dragComponent
1813+ dragComponentProperties: { "appDelegate": appDelegate }
1814+
1815+ onPressed: {
1816+ function matchDelegate(obj) { return String(obj.objectName).indexOf("appDelegate") >= 0; }
1817+
1818+ var delegateAtCenter = Functions.itemAt(appContainer, x, y, matchDelegate);
1819+ if (!delegateAtCenter) return;
1820+
1821+ appDelegate = delegateAtCenter;
1822+ }
1823+
1824+ onClicked: {
1825+ if (sideStage.shown) {
1826+ sideStage.hide();
1827+ } else {
1828+ sideStage.show();
1829+ priv.updateMainAndSideStageIndexes()
1830+ }
1831+ }
1832+
1833+ onDragStarted: {
1834+ // If we're dragging to the sidestage.
1835+ if (!sideStage.shown) {
1836+ sideStage.show();
1837+ }
1838+ }
1839+
1840+ Component {
1841+ id: dragComponent
1842+ SurfaceContainer {
1843+ property Item appDelegate
1844+
1845+ surface: appDelegate ? appDelegate.surface : null
1846+
1847+ consumesInput: false
1848+ interactive: false
1849+ focus: false
1850+ requestedWidth: appDelegate.requestedWidth
1851+ requestedHeight: appDelegate.requestedHeight
1852+
1853+ width: units.gu(40)
1854+ height: units.gu(40)
1855+
1856+ Drag.hotSpot.x: width/2
1857+ Drag.hotSpot.y: height/2
1858+ // only accept opposite stage.
1859+ Drag.keys: {
1860+ if (!surface) return "Disabled";
1861+ if (appDelegate.isDash) return "Disabled";
1862+
1863+ if (appDelegate.stage === ApplicationInfo.MainStage) {
1864+ if (appDelegate.application.supportedOrientations
1865+ & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1866+ return "MainStage";
1867+ }
1868+ return "Disabled";
1869+ }
1870+ return "SideStage";
1871+ }
1872+ }
1873+ }
1874+ }
1875+}