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