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