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