2 * Copyright (C) 2014-2017 Canonical, Ltd.
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.
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.
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/>.
18 import Ubuntu.Components 1.3
19 import Unity.Application 0.1
20 import "../Components/PanelState"
21 import "../Components"
23 import Ubuntu.Gestures 0.1
24 import GlobalShortcut 1.0
27 import "Spread/MathUtils.js" as MathUtils
28 import WindowManager 1.0
34 property QtObject applicationManager
35 property QtObject topLevelSurfaceList
36 property bool altTabPressed
37 property url background
38 property int dragAreaWidth
39 property bool interactive
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 bool oskEnabled: false
48 property rect inputMethodRect
49 property real rightEdgePushProgress: 0
50 property Item availableDesktopArea
53 property string mode: "staged"
55 // Used by the tutorial code
56 readonly property real rightEdgeDragProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0 // How far left the stage has been dragged
58 // used by the snap windows (edge maximize) feature
59 readonly property alias previewRectangle: fakeRectangle
61 readonly property bool spreadShown: state == "spread"
62 readonly property var mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
64 // application windows never rotate independently
65 property int mainAppWindowOrientationAngle: shellOrientationAngle
67 property bool orientationChangesEnabled: !priv.focusedAppDelegate || priv.focusedAppDelegate.orientationChangesEnabled
69 property int supportedOrientations: {
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;
86 return Qt.PortraitOrientation |
87 Qt.LandscapeOrientation |
88 Qt.InvertedPortraitOrientation |
89 Qt.InvertedLandscapeOrientation;
93 onAltTabPressedChanged: {
96 if (root.spreadEnabled) {
97 altTabDelayTimer.start();
100 // Alt Tab has been released, did we already go to spread?
101 if (priv.goneToSpread) {
102 priv.goneToSpread = false;
104 // No we didn't, do a quick alt-tab
105 if (appRepeater.count > 1) {
106 appRepeater.itemAt(1).activate();
107 } else if (appRepeater.count > 0) {
108 appRepeater.itemAt(0).activate(); // quick alt-tab to the only (minimized) window should still activate it
119 if (root.altTabPressed) {
120 priv.goneToSpread = true;
125 // For MirAL window management
127 normal: Qt.rect(0, root.mode === "windowed" ? priv.windowDecorationHeight : 0, 0, 0)
131 property Item itemConfiningMouseCursor: !spreadShown && priv.focusedAppDelegate && priv.focusedAppDelegate.window.confinesMousePointer ?
132 priv.focusedAppDelegate.clientAreaItem : null;
134 signal itemSnapshotRequested(Item item)
136 // functions to be called from outside
137 function updateFocusedAppOrientation() { /* TODO */ }
138 function updateFocusedAppOrientationAnimated() { /* TODO */}
140 function closeSpread() {
141 priv.goneToSpread = false;
144 onSpreadEnabledChanged: {
145 if (!spreadEnabled && spreadShown) {
150 onRightEdgePushProgressChanged: {
151 if (spreadEnabled && rightEdgePushProgress >= 1) {
152 priv.goneToSpread = true
157 id: lifecycleExceptions
158 schema.id: "com.canonical.qtmir"
161 function isExemptFromLifecycle(appId) {
162 var shortAppId = appId.split('_')[0];
163 for (var i = 0; i < lifecycleExceptions.lifecycleExemptAppids.length; i++) {
164 if (shortAppId === lifecycleExceptions.lifecycleExemptAppids[i]) {
172 id: closeFocusedShortcut
173 shortcut: Qt.AltModifier|Qt.Key_F4
175 if (priv.focusedAppDelegate) {
176 priv.focusedAppDelegate.close();
182 id: showSpreadShortcut
183 shortcut: Qt.MetaModifier|Qt.Key_W
184 active: root.spreadEnabled
185 onTriggered: priv.goneToSpread = true
189 id: minimizeAllShortcut
190 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
191 onTriggered: priv.minimizeAllWindows()
192 active: root.state == "windowed"
196 id: maximizeWindowShortcut
197 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
198 onTriggered: priv.focusedAppDelegate.requestMaximize()
199 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximized
203 id: maximizeWindowLeftShortcut
204 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
205 onTriggered: priv.focusedAppDelegate.requestMaximizeLeft()
206 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
210 id: maximizeWindowRightShortcut
211 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
212 onTriggered: priv.focusedAppDelegate.requestMaximizeRight()
213 active: root.state == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.canBeMaximizedLeftRight
217 id: minimizeRestoreShortcut
218 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
220 if (priv.focusedAppDelegate.anyMaximized) {
221 priv.focusedAppDelegate.requestRestore();
223 priv.focusedAppDelegate.requestMinimize();
226 active: root.state == "windowed" && priv.focusedAppDelegate
230 shortcut: Qt.AltModifier|Qt.Key_Print
231 onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
232 active: priv.focusedAppDelegate !== null
236 shortcut: Qt.ControlModifier|Qt.AltModifier|Qt.Key_T
238 // try in this order: snap pkg, new deb name, old deb name
239 var candidates = ["ubuntu-terminal-app_ubuntu-terminal-app", "ubuntu-terminal-app", "com.ubuntu.terminal"];
240 for (var i = 0; i < candidates.length; i++) {
241 if (priv.startApp(candidates[i]))
249 objectName: "DesktopStagePrivate"
251 function startApp(appId) {
252 if (root.applicationManager.findApplication(appId)) {
253 return root.applicationManager.requestFocusApplication(appId);
255 return root.applicationManager.startApplication(appId) !== null;
259 property var focusedAppDelegate: null
260 property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
262 property bool goneToSpread: false
263 property int closingIndex: -1
264 property int animationDuration: UbuntuAnimation.FastDuration
266 function updateForegroundMaximizedApp() {
268 for (var i = 0; i < appRepeater.count && !found; i++) {
269 var item = appRepeater.itemAt(i);
270 if (item && item.visuallyMaximized) {
271 foregroundMaximizedAppDelegate = item;
276 foregroundMaximizedAppDelegate = null;
280 function minimizeAllWindows() {
281 for (var i = appRepeater.count - 1; i >= 0; i--) {
282 var appDelegate = appRepeater.itemAt(i);
283 if (appDelegate && !appDelegate.minimized) {
284 appDelegate.requestMinimize();
289 readonly property bool sideStageEnabled: root.mode === "stagedWithSideStage" &&
290 (root.shellOrientation == Qt.LandscapeOrientation ||
291 root.shellOrientation == Qt.InvertedLandscapeOrientation)
292 onSideStageEnabledChanged: {
293 for (var i = 0; i < appRepeater.count; i++) {
294 appRepeater.itemAt(i).refreshStage();
296 priv.updateMainAndSideStageIndexes();
299 property var mainStageDelegate: null
300 property var sideStageDelegate: null
301 property int mainStageItemId: 0
302 property int sideStageItemId: 0
303 property string mainStageAppId: ""
304 property string sideStageAppId: ""
306 onSideStageDelegateChanged: {
307 if (!sideStageDelegate) {
312 function updateMainAndSideStageIndexes() {
313 if (root.mode != "stagedWithSideStage") {
314 priv.sideStageDelegate = null;
315 priv.sideStageItemId = 0;
316 priv.sideStageAppId = "";
317 priv.mainStageDelegate = appRepeater.itemAt(0);
318 priv.mainStageItemId = topLevelSurfaceList.idAt(0);
319 priv.mainStageAppId = topLevelSurfaceList.applicationAt(0) ? topLevelSurfaceList.applicationAt(0).appId : ""
323 var choseMainStage = false;
324 var choseSideStage = false;
326 if (!root.topLevelSurfaceList)
329 for (var i = 0; i < appRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
330 var appDelegate = appRepeater.itemAt(i);
332 // This might happen during startup phase... If the delegate appears and claims focus
333 // things are updated and appRepeater.itemAt(x) still returns null while appRepeater.count >= x
334 // Lets just skip it, on startup it will be generated at a later point too...
337 if (sideStage.shown && appDelegate.stage == ApplicationInfoInterface.SideStage
338 && !choseSideStage) {
339 priv.sideStageDelegate = appDelegate
340 priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
341 priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
342 choseSideStage = true;
343 } else if (!choseMainStage && appDelegate.stage == ApplicationInfoInterface.MainStage) {
344 priv.mainStageDelegate = appDelegate;
345 priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
346 priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
347 choseMainStage = true;
350 if (!choseMainStage && priv.mainStageDelegate) {
351 priv.mainStageDelegate = null;
352 priv.mainStageItemId = 0;
353 priv.mainStageAppId = "";
355 if (!choseSideStage && priv.sideStageDelegate) {
356 priv.sideStageDelegate = null;
357 priv.sideStageItemId = 0;
358 priv.sideStageAppId = "";
362 property int nextInStack: {
363 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.itemIndex : -1;
364 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.itemIndex : -1;
365 if (sideStageIndex == -1) {
366 return topLevelSurfaceList.count > 1 ? 1 : -1;
368 if (mainStageIndex == 0 || sideStageIndex == 0) {
369 if (mainStageIndex == 1 || sideStageIndex == 1) {
370 return topLevelSurfaceList.count > 2 ? 2 : -1;
377 readonly property real virtualKeyboardHeight: root.inputMethodRect.height
379 readonly property real windowDecorationHeight: units.gu(3)
382 Component.onCompleted: priv.updateMainAndSideStageIndexes();
386 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
387 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestMinimize(); } }
388 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.requestRestore(); } }
393 property: "decorationsVisible"
394 value: mode == "windowed" && priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && !root.spreadShown
401 if (priv.focusedAppDelegate !== null) {
402 if (priv.focusedAppDelegate.maximized)
403 return priv.focusedAppDelegate.title
405 return priv.focusedAppDelegate.appName
409 when: priv.focusedAppDelegate
414 property: "focusedPersistentSurfaceId"
416 if (priv.focusedAppDelegate !== null) {
417 if (priv.focusedAppDelegate.surface) {
418 return priv.focusedAppDelegate.surface.persistentId;
423 when: priv.focusedAppDelegate
428 property: "dropShadow"
429 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null && mode == "windowed"
434 property: "closeButtonShown"
435 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized
438 Component.onDestruction: {
439 PanelState.title = "";
440 PanelState.decorationsVisible = false;
441 PanelState.dropShadow = false;
445 model: root.applicationManager
447 property var stateBinding: Binding {
448 readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
449 target: model.application
450 property: "requestedState"
452 // TODO: figure out some lifecycle policy, like suspending minimized apps
453 // or something if running windowed.
454 // TODO: If the device has a dozen suspended apps because it was running
455 // in staged mode, when it switches to Windowed mode it will suddenly
456 // resume all those apps at once. We might want to avoid that.
457 value: root.mode === "windowed"
459 || (!root.suspended && model.application && priv.focusedAppDelegate &&
460 (priv.focusedAppDelegate.appId === model.application.appId ||
461 priv.mainStageAppId === model.application.appId ||
462 priv.sideStageAppId === model.application.appId))
463 ? ApplicationInfoInterface.RequestedRunning
464 : ApplicationInfoInterface.RequestedSuspended
467 property var lifecycleBinding: Binding {
468 target: model.application
469 property: "exemptFromLifecycle"
470 value: model.application
471 ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
479 name: "spread"; when: priv.goneToSpread
480 PropertyChanges { target: floatingFlickable; enabled: true }
481 PropertyChanges { target: root; focus: true }
482 PropertyChanges { target: spreadItem; focus: true }
483 PropertyChanges { target: hoverMouseArea; enabled: true }
484 PropertyChanges { target: rightEdgeDragArea; enabled: false }
485 PropertyChanges { target: cancelSpreadMouseArea; enabled: true }
486 PropertyChanges { target: blurLayer; visible: true; blurRadius: 32; brightness: .65; opacity: 1 }
487 PropertyChanges { target: wallpaper; visible: false }
490 name: "stagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "staged"
500 name: "sideStagedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "stagedWithSideStage"
501 extend: "stagedRightEdge"
504 opacity: priv.sideStageDelegate && priv.sideStageDelegate.x === sideStage.x ? 1 : 0
509 name: "windowedRightEdge"; when: root.spreadEnabled && (rightEdgeDragArea.dragging || rightEdgePushProgress > 0) && root.mode == "windowed"
515 opacity: MathUtils.linearAnimation(spreadItem.rightEdgeBreakPoint, 1, 0, 1, Math.max(rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0, rightEdgePushProgress))
519 name: "staged"; when: root.mode === "staged"
520 PropertyChanges { target: wallpaper; visible: !priv.focusedAppDelegate || priv.focusedAppDelegate.x !== 0 }
521 PropertyChanges { target: root; focus: true }
522 PropertyChanges { target: appContainer; focus: true }
525 name: "stagedWithSideStage"; when: root.mode === "stagedWithSideStage"
526 PropertyChanges { target: triGestureArea; enabled: priv.sideStageEnabled }
527 PropertyChanges { target: sideStage; visible: true }
528 PropertyChanges { target: root; focus: true }
529 PropertyChanges { target: appContainer; focus: true }
532 name: "windowed"; when: root.mode === "windowed"
533 PropertyChanges { target: root; focus: true }
534 PropertyChanges { target: appContainer; focus: true }
539 from: "stagedRightEdge,sideStagedRightEdge,windowedRightEdge"; to: "spread"
540 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
541 PropertyAnimation { target: blurLayer; properties: "brightness,blurRadius"; duration: priv.animationDuration }
545 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: appRepeater.count > 1 ? 1 : 0 }
549 SequentialAnimation {
552 var item = appRepeater.itemAt(Math.max(0, spreadItem.highlightedIndex));
553 if (item.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
556 item.playFocusAnimation();
559 PropertyAction { target: spreadItem; property: "highlightedIndex"; value: -1 }
560 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
564 to: "stagedRightEdge,sideStagedRightEdge"
565 PropertyAction { target: floatingFlickable; property: "contentX"; value: 0 }
568 to: "stagedWithSideStage"
569 ScriptAction { script: priv.updateMainAndSideStageIndexes(); }
575 id: cancelSpreadMouseArea
578 onClicked: priv.goneToSpread = false
583 objectName: "appContainer"
590 source: root.background
591 // Make sure it's the lowest item. Due to the left edge drag we sometimes need
592 // to put the dash at -1 and we don't want it behind the Wallpaper
605 objectName: "spreadItem"
606 anchors.fill: appContainer
607 leftMargin: root.availableDesktopArea.x
608 model: root.topLevelSurfaceList
609 spreadFlickable: floatingFlickable
613 priv.goneToSpread = false;
617 appRepeater.itemAt(highlightedIndex).close();
622 target: root.topLevelSurfaceList
623 onListChanged: priv.updateMainAndSideStageIndexes()
628 objectName: "MainStageDropArea"
632 bottom: parent.bottom
634 width: appContainer.width - sideStage.width
635 enabled: priv.sideStageEnabled
638 drop.source.appDelegate.saveStage(ApplicationInfoInterface.MainStage);
639 drop.source.appDelegate.focus = true;
646 objectName: "sideStage"
648 height: appContainer.height
649 x: appContainer.width - width
651 Behavior on opacity { UbuntuNumberAnimation {} }
653 if (!priv.mainStageItemId) return 0;
655 if (priv.sideStageItemId && priv.nextInStack > 0) {
657 // Due the order in which bindings are evaluated, this might be triggered while shuffling
658 // the list and index doesn't yet match with itemIndex (even though itemIndex: index)
659 // Let's walk the list and compare itemIndex to make sure we have the correct one.
660 var nextDelegateInStack = -1;
661 for (var i = 0; i < appRepeater.count; i++) {
662 if (appRepeater.itemAt(i).itemIndex == priv.nextInStack) {
663 nextDelegateInStack = appRepeater.itemAt(i);
668 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
669 // if the next app in stack is a main stage app, put the sidestage on top of it.
679 if (!shown && priv.mainStageDelegate && !root.spreadShown) {
680 priv.mainStageDelegate.activate();
685 id: sideStageDropArea
686 objectName: "SideStageDropArea"
689 property bool dropAllowed: true
692 dropAllowed = drag.keys != "Disabled";
698 if (drop.keys == "MainStage") {
699 drop.source.appDelegate.saveStage(ApplicationInfoInterface.SideStage);
700 drop.source.appDelegate.focus = true;
705 if (!sideStageDropArea.drag.source) {
715 model: topLevelSurfaceList
716 objectName: "appRepeater"
718 function indexOf(delegateItem) {
719 for (var i = 0; i < count; i++) {
720 if (itemAt(i) === delegateItem) {
727 delegate: FocusScope {
729 objectName: "appDelegate_" + model.window.id
730 property int itemIndex: index // We need this from outside the repeater
731 // z might be overriden in some cases by effects, but we need z ordering
732 // to calculate occlusion detection
733 property int normalZ: topLevelSurfaceList.count - index
735 if (visuallyMaximized) {
736 priv.updateForegroundMaximizedApp();
741 // Normally we want x/y where the surface thinks it is. Width/height of our delegate will
742 // match what the actual surface size is.
743 // Don't write to those, they will be set by states
744 x: model.window.position.x - clientAreaItem.x
745 y: model.window.position.y - clientAreaItem.y
746 width: decoratedWindow.implicitWidth
747 height: decoratedWindow.implicitHeight
749 // requestedX/Y/width/height is what we ask the actual surface to be.
750 // Do not write to those, they will be set by states
751 property real requestedX: windowedX
752 property real requestedY: windowedY
753 property real requestedWidth: windowedWidth
754 property real requestedHeight: windowedHeight
756 target: model.window; property: "requestedPosition"
757 // miral doesn't know about our window decorations. So we have to deduct them
758 value: Qt.point(appDelegate.requestedX + appDelegate.clientAreaItem.x,
759 appDelegate.requestedY + appDelegate.clientAreaItem.y)
760 when: root.mode == "windowed"
763 // In those are for windowed mode. Those values basically store the window's properties
764 // when having a floating window. If you want to move/resize a window in normal mode, this is what you want to write to.
765 property real windowedX
766 property real windowedY
767 property real windowedWidth
768 property real windowedHeight
770 // unlike windowedX/Y, this is the last known grab position before being pushed against edges/corners
771 // when restoring, the window should return to these, not to the place where it was dropped near the edge
772 property real restoredX
773 property real restoredY
775 // Keeps track of the window geometry while in normal or restored state
776 // Useful when returning from some maxmized state or when saving the geometry while maximized
777 // FIXME: find a better solution
778 property real normalX: 0
779 property real normalY: 0
780 property real normalWidth: 0
781 property real normalHeight: 0
782 function updateNormalGeometry() {
783 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
784 normalX = appDelegate.requestedX;
785 normalY = appDelegate.requestedY;
786 normalWidth = appDelegate.width;
787 normalHeight = appDelegate.height;
790 function updateRestoredGeometry() {
791 if (appDelegate.state == "normal" || appDelegate.state == "restored") {
792 // save the x/y to restore to
793 restoredX = appDelegate.x;
794 restoredY = appDelegate.y;
800 onXChanged: appDelegate.updateNormalGeometry();
801 onYChanged: appDelegate.updateNormalGeometry();
802 onWidthChanged: appDelegate.updateNormalGeometry();
803 onHeightChanged: appDelegate.updateNormalGeometry();
809 value: appDelegate.requestedY -
810 Math.min(appDelegate.requestedY - root.availableDesktopArea.y,
811 Math.max(0, priv.virtualKeyboardHeight - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
812 when: root.oskEnabled && appDelegate.focus && (appDelegate.state == "normal" || appDelegate.state == "restored")
813 && root.inputMethodRect.height > 0
816 Behavior on x { id: xBehavior; enabled: priv.closingIndex >= 0; UbuntuNumberAnimation { onRunningChanged: if (!running) priv.closingIndex = -1} }
820 onShellOrientationAngleChanged: {
821 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
822 if (application && application.rotatesWindowContents) {
823 if (root.state == "windowed") {
824 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
825 angleDiff = (360 + angleDiff) % 360;
826 if (angleDiff === 90 || angleDiff === 270) {
827 var aux = decoratedWindow.requestedHeight;
828 decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.actualDecorationHeight;
829 decoratedWindow.requestedWidth = aux - decoratedWindow.actualDecorationHeight;
832 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
834 decoratedWindow.surfaceOrientationAngle = 0;
839 readonly property alias application: decoratedWindow.application
840 readonly property alias minimumWidth: decoratedWindow.minimumWidth
841 readonly property alias minimumHeight: decoratedWindow.minimumHeight
842 readonly property alias maximumWidth: decoratedWindow.maximumWidth
843 readonly property alias maximumHeight: decoratedWindow.maximumHeight
844 readonly property alias widthIncrement: decoratedWindow.widthIncrement
845 readonly property alias heightIncrement: decoratedWindow.heightIncrement
847 readonly property bool maximized: windowState === WindowStateStorage.WindowStateMaximized
848 readonly property bool maximizedLeft: windowState === WindowStateStorage.WindowStateMaximizedLeft
849 readonly property bool maximizedRight: windowState === WindowStateStorage.WindowStateMaximizedRight
850 readonly property bool maximizedHorizontally: windowState === WindowStateStorage.WindowStateMaximizedHorizontally
851 readonly property bool maximizedVertically: windowState === WindowStateStorage.WindowStateMaximizedVertically
852 readonly property bool maximizedTopLeft: windowState === WindowStateStorage.WindowStateMaximizedTopLeft
853 readonly property bool maximizedTopRight: windowState === WindowStateStorage.WindowStateMaximizedTopRight
854 readonly property bool maximizedBottomLeft: windowState === WindowStateStorage.WindowStateMaximizedBottomLeft
855 readonly property bool maximizedBottomRight: windowState === WindowStateStorage.WindowStateMaximizedBottomRight
856 readonly property bool anyMaximized: maximized || maximizedLeft || maximizedRight || maximizedHorizontally || maximizedVertically ||
857 maximizedTopLeft || maximizedTopRight || maximizedBottomLeft || maximizedBottomRight
859 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
860 readonly property bool fullscreen: windowState === WindowStateStorage.WindowStateFullscreen
862 readonly property bool canBeMaximized: canBeMaximizedHorizontally && canBeMaximizedVertically
863 readonly property bool canBeMaximizedLeftRight: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
864 (maximumHeight == 0 || maximumHeight >= appContainer.height)
865 readonly property bool canBeCornerMaximized: (maximumWidth == 0 || maximumWidth >= appContainer.width/2) &&
866 (maximumHeight == 0 || maximumHeight >= appContainer.height/2)
867 readonly property bool canBeMaximizedHorizontally: maximumWidth == 0 || maximumWidth >= appContainer.width
868 readonly property bool canBeMaximizedVertically: maximumHeight == 0 || maximumHeight >= appContainer.height
869 readonly property alias orientationChangesEnabled: decoratedWindow.orientationChangesEnabled
871 // TODO drop our own windowType once Mir/Miral/Qtmir gets in sync with ours
872 property int windowState: WindowStateStorage.WindowStateNormal
873 property int prevWindowState: WindowStateStorage.WindowStateRestored
875 property bool animationsEnabled: true
876 property alias title: decoratedWindow.title
877 readonly property string appName: model.application ? model.application.name : ""
878 property bool visuallyMaximized: false
879 property bool visuallyMinimized: false
880 readonly property alias windowedTransitionRunning: windowedTransition.running
882 property int stage: ApplicationInfoInterface.MainStage
883 function saveStage(newStage) {
884 appDelegate.stage = newStage;
885 WindowStateStorage.saveStage(appId, newStage);
886 priv.updateMainAndSideStageIndexes()
889 readonly property var surface: model.window.surface
890 readonly property var window: model.window
892 readonly property alias focusedSurface: decoratedWindow.focusedSurface
893 readonly property bool dragging: touchControls.overlayShown ? touchControls.dragging : decoratedWindow.dragging
895 readonly property string appId: model.application.appId
896 readonly property bool isDash: appId == "unity8-dash"
897 readonly property alias clientAreaItem: decoratedWindow.clientAreaItem
899 function activate() {
900 if (model.window.focused) {
901 updateQmlFocusFromMirSurfaceFocus();
903 model.window.activate();
906 function requestMaximize() { model.window.requestState(Mir.MaximizedState); }
907 function requestMaximizeVertically() { model.window.requestState(Mir.VertMaximizedState); }
908 function requestMaximizeHorizontally() { model.window.requestState(Mir.HorizMaximizedState); }
909 function requestMaximizeLeft() { model.window.requestState(Mir.MaximizedLeftState); }
910 function requestMaximizeRight() { model.window.requestState(Mir.MaximizedRightState); }
911 function requestMaximizeTopLeft() { model.window.requestState(Mir.MaximizedTopLeftState); }
912 function requestMaximizeTopRight() { model.window.requestState(Mir.MaximizedTopRightState); }
913 function requestMaximizeBottomLeft() { model.window.requestState(Mir.MaximizedBottomLeftState); }
914 function requestMaximizeBottomRight() { model.window.requestState(Mir.MaximizedBottomRightState); }
915 function requestMinimize() { model.window.requestState(Mir.MinimizedState); }
916 function requestRestore() { model.window.requestState(Mir.RestoredState); }
918 function claimFocus() {
919 if (root.state == "spread") {
920 spreadItem.highlightedIndex = index
921 priv.goneToSpread = false;
923 if (root.mode == "stagedWithSideStage") {
924 if (appDelegate.stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
927 priv.updateMainAndSideStageIndexes();
929 appDelegate.focus = true;
930 priv.focusedAppDelegate = appDelegate;
933 function updateQmlFocusFromMirSurfaceFocus() {
934 if (model.window.focused) {
936 decoratedWindow.focus = true;
943 screenWidth: appContainer.width
944 screenHeight: appContainer.height
945 leftMargin: root.availableDesktopArea.x
946 minimumY: root.availableDesktopArea.y
952 updateQmlFocusFromMirSurfaceFocus();
955 appDelegate.activate();
958 if (model.window.state === Mir.MinimizedState) {
959 appDelegate.minimize();
960 } else if (model.window.state === Mir.MaximizedState) {
961 appDelegate.maximize();
962 } else if (model.window.state === Mir.VertMaximizedState) {
963 appDelegate.maximizeVertically();
964 } else if (model.window.state === Mir.HorizMaximizedState) {
965 appDelegate.maximizeHorizontally();
966 } else if (model.window.state === Mir.MaximizedLeftState) {
967 appDelegate.maximizeLeft();
968 } else if (model.window.state === Mir.MaximizedRightState) {
969 appDelegate.maximizeRight();
970 } else if (model.window.state === Mir.MaximizedTopLeftState) {
971 appDelegate.maximizeTopLeft();
972 } else if (model.window.state === Mir.MaximizedTopRightState) {
973 appDelegate.maximizeTopRight();
974 } else if (model.window.state === Mir.MaximizedBottomLeftState) {
975 appDelegate.maximizeBottomLeft();
976 } else if (model.window.state === Mir.MaximizedBottomRightState) {
977 appDelegate.maximizeBottomRight();
978 } else if (model.window.state === Mir.RestoredState) {
979 if (appDelegate.fullscreen && appDelegate.prevWindowState != WindowStateStorage.WindowStateRestored
980 && appDelegate.prevWindowState != WindowStateStorage.WindowStateNormal) {
981 model.window.requestState(WindowStateStorage.toMirState(appDelegate.prevWindowState));
983 appDelegate.restore();
985 } else if (model.window.state === Mir.FullscreenState) {
986 appDelegate.prevWindowState = appDelegate.windowState;
987 appDelegate.windowState = WindowStateStorage.WindowStateFullscreen;
992 readonly property bool windowReady: clientAreaItem.surfaceInitialized
993 onWindowReadyChanged: {
995 var loadedMirState = WindowStateStorage.toMirState(windowStateSaver.loadedState);
996 // need to apply the shell chrome policy on top the saved window state
998 if (root.mode == "windowed") {
999 policy = windowedFullscreenPolicy;
1001 policy = stagedFullscreenPolicy
1003 window.requestState(policy.applyPolicy(loadedMirState, surface.shellChrome));
1007 Component.onCompleted: {
1008 if (application && application.rotatesWindowContents) {
1009 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
1011 decoratedWindow.surfaceOrientationAngle = 0;
1014 // First, cascade the newly created window, relative to the currently/old focused window.
1015 windowedX = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedX + units.gu(3) : (normalZ - 1) * units.gu(3)
1016 windowedY = priv.focusedAppDelegate ? priv.focusedAppDelegate.windowedY + units.gu(3) : normalZ * units.gu(3)
1017 // Now load any saved state. This needs to happen *after* the cascading!
1018 windowStateSaver.load();
1020 updateQmlFocusFromMirSurfaceFocus();
1023 _constructing = false;
1025 Component.onDestruction: {
1026 windowStateSaver.save();
1029 // This stage is about to be destroyed. Don't mess up with the model at this point
1033 if (visuallyMaximized) {
1034 priv.updateForegroundMaximizedApp();
1038 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
1040 property bool _constructing: true;
1042 if (!_constructing) {
1043 priv.updateMainAndSideStageIndexes();
1049 && !greeter.fullyShown
1050 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
1052 || appDelegate.fullscreen
1053 || focusAnimation.running || rightEdgeFocusAnimation.running || hidingAnimation.running
1056 model.window.close();
1059 function maximize(animated) {
1060 animationsEnabled = (animated === undefined) || animated;
1061 windowState = WindowStateStorage.WindowStateMaximized;
1063 function maximizeLeft(animated) {
1064 animationsEnabled = (animated === undefined) || animated;
1065 windowState = WindowStateStorage.WindowStateMaximizedLeft;
1067 function maximizeRight(animated) {
1068 animationsEnabled = (animated === undefined) || animated;
1069 windowState = WindowStateStorage.WindowStateMaximizedRight;
1071 function maximizeHorizontally(animated) {
1072 animationsEnabled = (animated === undefined) || animated;
1073 windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
1075 function maximizeVertically(animated) {
1076 animationsEnabled = (animated === undefined) || animated;
1077 windowState = WindowStateStorage.WindowStateMaximizedVertically;
1079 function maximizeTopLeft(animated) {
1080 animationsEnabled = (animated === undefined) || animated;
1081 windowState = WindowStateStorage.WindowStateMaximizedTopLeft;
1083 function maximizeTopRight(animated) {
1084 animationsEnabled = (animated === undefined) || animated;
1085 windowState = WindowStateStorage.WindowStateMaximizedTopRight;
1087 function maximizeBottomLeft(animated) {
1088 animationsEnabled = (animated === undefined) || animated;
1089 windowState = WindowStateStorage.WindowStateMaximizedBottomLeft;
1091 function maximizeBottomRight(animated) {
1092 animationsEnabled = (animated === undefined) || animated;
1093 windowState = WindowStateStorage.WindowStateMaximizedBottomRight;
1095 function minimize(animated) {
1096 animationsEnabled = (animated === undefined) || animated;
1097 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
1099 function restore(animated,state) {
1100 animationsEnabled = (animated === undefined) || animated;
1101 windowState = state || WindowStateStorage.WindowStateRestored;
1102 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
1103 prevWindowState = windowState;
1106 function playFocusAnimation() {
1107 if (state == "stagedRightEdge") {
1108 // TODO: Can we drop this if and find something that always works?
1109 if (root.mode == "staged") {
1110 rightEdgeFocusAnimation.targetX = 0
1111 rightEdgeFocusAnimation.start()
1112 } else if (root.mode == "stagedWithSideStage") {
1113 rightEdgeFocusAnimation.targetX = appDelegate.stage == ApplicationInfoInterface.SideStage ? sideStage.x : 0
1114 rightEdgeFocusAnimation.start()
1116 } else if (state == "windowedRightEdge" || state == "windowed") {
1119 focusAnimation.start()
1122 function playHidingAnimation() {
1123 if (state != "windowedRightEdge") {
1124 hidingAnimation.start()
1128 function refreshStage() {
1129 var newStage = ApplicationInfoInterface.MainStage;
1130 if (priv.sideStageEnabled) { // we're in lanscape rotation.
1131 if (!isDash && application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1132 var defaultStage = ApplicationInfoInterface.SideStage; // if application supports portrait, it defaults to sidestage.
1133 if (application.supportedOrientations & (Qt.LandscapeOrientation|Qt.InvertedLandscapeOrientation)) {
1134 // if it supports lanscape, it defaults to mainstage.
1135 defaultStage = ApplicationInfoInterface.MainStage;
1137 newStage = WindowStateStorage.getStage(application.appId, defaultStage);
1142 if (focus && stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
1147 UbuntuNumberAnimation {
1153 duration: UbuntuAnimation.SnapDuration
1155 topLevelSurfaceList.raiseId(model.window.id);
1158 appDelegate.activate();
1162 id: rightEdgeFocusAnimation
1163 property int targetX: 0
1164 UbuntuNumberAnimation { target: appDelegate; properties: "x"; to: rightEdgeFocusAnimation.targetX; duration: priv.animationDuration }
1165 UbuntuNumberAnimation { target: decoratedWindow; properties: "angle"; to: 0; duration: priv.animationDuration }
1166 UbuntuNumberAnimation { target: decoratedWindow; properties: "itemScale"; to: 1; duration: priv.animationDuration }
1168 appDelegate.activate();
1173 UbuntuNumberAnimation { target: appDelegate; property: "opacity"; to: 0; duration: priv.animationDuration }
1174 onStopped: appDelegate.opacity = 1
1181 flickable: floatingFlickable
1185 sceneWidth: root.width
1186 stage: appDelegate.stage
1187 thisDelegate: appDelegate
1188 mainStageDelegate: priv.mainStageDelegate
1189 sideStageDelegate: priv.sideStageDelegate
1190 sideStageWidth: sideStage.panelWidth
1191 sideStageX: sideStage.x
1192 itemIndex: appDelegate.itemIndex
1193 nextInStack: priv.nextInStack
1196 StagedRightEdgeMaths {
1197 id: stagedRightEdgeMaths
1198 sceneWidth: root.availableDesktopArea.width
1199 sceneHeight: appContainer.height
1200 isMainStageApp: priv.mainStageDelegate == appDelegate
1201 isSideStageApp: priv.sideStageDelegate == appDelegate
1202 sideStageWidth: sideStage.width
1203 sideStageOpen: sideStage.shown
1205 nextInStack: priv.nextInStack
1207 targetHeight: spreadItem.stackHeight
1208 targetX: spreadMaths.targetX
1209 startY: appDelegate.fullscreen ? 0 : root.availableDesktopArea.y
1210 targetY: spreadMaths.targetY
1211 targetAngle: spreadMaths.targetAngle
1212 targetScale: spreadMaths.targetScale
1213 shuffledZ: stageMaths.itemZ
1214 breakPoint: spreadItem.rightEdgeBreakPoint
1217 WindowedRightEdgeMaths {
1218 id: windowedRightEdgeMaths
1220 startWidth: appDelegate.requestedWidth
1221 startHeight: appDelegate.requestedHeight
1222 targetHeight: spreadItem.stackHeight
1223 targetX: spreadMaths.targetX
1224 targetY: spreadMaths.targetY
1225 normalZ: appDelegate.normalZ
1226 targetAngle: spreadMaths.targetAngle
1227 targetScale: spreadMaths.targetScale
1228 breakPoint: spreadItem.rightEdgeBreakPoint
1233 name: "spread"; when: root.state == "spread"
1234 StateChangeScript { script: { decoratedWindow.cancelDrag(); } }
1236 target: decoratedWindow;
1237 showDecoration: false;
1238 angle: spreadMaths.targetAngle
1239 itemScale: spreadMaths.targetScale
1240 scaleToPreviewSize: spreadItem.stackHeight
1241 scaleToPreviewProgress: 1
1242 hasDecoration: root.mode === "windowed"
1243 shadowOpacity: spreadMaths.shadowOpacity
1244 showHighlight: spreadItem.highlightedIndex === index
1245 darkening: spreadItem.highlightedIndex >= 0
1246 anchors.topMargin: dragArea.distance
1251 x: spreadMaths.targetX
1252 y: spreadMaths.targetY
1254 height: spreadItem.spreadItemHeight
1255 requestedWidth: decoratedWindow.oldRequestedWidth
1256 requestedHeight: decoratedWindow.oldRequestedHeight
1257 visible: spreadMaths.itemVisible
1259 PropertyChanges { target: dragArea; enabled: true }
1260 PropertyChanges { target: windowInfoItem; opacity: spreadMaths.tileInfoOpacity; visible: spreadMaths.itemVisible }
1261 PropertyChanges { target: touchControls; enabled: false }
1264 name: "stagedRightEdge"
1265 when: (root.mode == "staged" || root.mode == "stagedWithSideStage") && (root.state == "sideStagedRightEdge" || root.state == "stagedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running)
1267 target: stagedRightEdgeMaths
1268 progress: Math.max(rightEdgePushProgress, rightEdgeDragArea.draggedProgress)
1272 x: stagedRightEdgeMaths.animatedX
1273 y: stagedRightEdgeMaths.animatedY
1274 z: stagedRightEdgeMaths.animatedZ
1275 height: stagedRightEdgeMaths.animatedHeight
1276 requestedWidth: decoratedWindow.oldRequestedWidth
1277 requestedHeight: decoratedWindow.oldRequestedHeight
1278 visible: appDelegate.x < root.width
1281 target: decoratedWindow
1282 hasDecoration: false
1283 angle: stagedRightEdgeMaths.animatedAngle
1284 itemScale: stagedRightEdgeMaths.animatedScale
1285 scaleToPreviewSize: spreadItem.stackHeight
1286 scaleToPreviewProgress: stagedRightEdgeMaths.scaleToPreviewProgress
1290 // make sure it's visible but transparent so it fades in when we transition to spread
1291 PropertyChanges { target: windowInfoItem; opacity: 0; visible: true }
1294 name: "windowedRightEdge"
1295 when: root.mode == "windowed" && (root.state == "windowedRightEdge" || rightEdgeFocusAnimation.running || hidingAnimation.running || rightEdgePushProgress > 0)
1297 target: windowedRightEdgeMaths
1298 swipeProgress: rightEdgeDragArea.dragging ? rightEdgeDragArea.progress : 0
1299 pushProgress: rightEdgePushProgress
1303 x: windowedRightEdgeMaths.animatedX
1304 y: windowedRightEdgeMaths.animatedY
1305 z: windowedRightEdgeMaths.animatedZ
1306 height: stagedRightEdgeMaths.animatedHeight
1307 requestedWidth: decoratedWindow.oldRequestedWidth
1308 requestedHeight: decoratedWindow.oldRequestedHeight
1311 target: decoratedWindow
1312 showDecoration: windowedRightEdgeMaths.decorationHeight
1313 angle: windowedRightEdgeMaths.animatedAngle
1314 itemScale: windowedRightEdgeMaths.animatedScale
1315 scaleToPreviewSize: spreadItem.stackHeight
1316 scaleToPreviewProgress: windowedRightEdgeMaths.scaleToPreviewProgress
1320 target: opacityEffect;
1321 opacityValue: windowedRightEdgeMaths.opacityMask
1322 sourceItem: windowedRightEdgeMaths.opacityMask < 1 ? decoratedWindow : null
1326 name: "staged"; when: root.state == "staged"
1330 y: root.availableDesktopArea.y
1331 requestedWidth: appContainer.width
1332 requestedHeight: root.availableDesktopArea.height
1333 visuallyMaximized: true
1334 visible: appDelegate.x < root.width
1337 target: decoratedWindow
1338 hasDecoration: false
1346 animateX: !focusAnimation.running && itemIndex !== spreadItem.highlightedIndex
1349 target: appDelegate.window
1350 allowClientResize: false
1354 name: "stagedWithSideStage"; when: root.state == "stagedWithSideStage"
1362 y: root.availableDesktopArea.y
1364 requestedWidth: stageMaths.itemWidth
1365 requestedHeight: root.availableDesktopArea.height
1366 visuallyMaximized: true
1367 visible: appDelegate.x < root.width
1370 target: decoratedWindow
1371 hasDecoration: false
1378 target: appDelegate.window
1379 allowClientResize: false
1383 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
1385 target: appDelegate;
1386 requestedX: root.availableDesktopArea.x;
1388 visuallyMinimized: false;
1389 requestedWidth: root.availableDesktopArea.width;
1390 requestedHeight: appContainer.height;
1392 PropertyChanges { target: touchControls; enabled: true }
1393 PropertyChanges { target: decoratedWindow; windowControlButtonsVisible: false }
1396 name: "fullscreen"; when: appDelegate.fullscreen && !appDelegate.minimized
1398 target: appDelegate;
1401 requestedWidth: appContainer.width;
1402 requestedHeight: appContainer.height;
1404 PropertyChanges { target: decoratedWindow; hasDecoration: false }
1408 when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
1411 visuallyMinimized: false
1413 PropertyChanges { target: touchControls; enabled: true }
1414 PropertyChanges { target: resizeArea; enabled: true }
1415 PropertyChanges { target: decoratedWindow; shadowOpacity: .3; windowControlButtonsVisible: true}
1419 when: appDelegate.windowState == WindowStateStorage.WindowStateRestored
1422 restoreEntryValues: false
1423 target: appDelegate;
1424 windowedX: restoredX;
1425 windowedY: restoredY;
1429 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
1433 windowedX: root.availableDesktopArea.x
1434 windowedY: root.availableDesktopArea.y
1435 windowedWidth: root.availableDesktopArea.width / 2
1436 windowedHeight: root.availableDesktopArea.height
1440 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
1441 extend: "maximizedLeft"
1443 target: appDelegate;
1444 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1448 name: "maximizedTopLeft"; when: appDelegate.maximizedTopLeft && !appDelegate.minimized
1452 windowedX: root.availableDesktopArea.x
1453 windowedY: root.availableDesktopArea.y
1454 windowedWidth: root.availableDesktopArea.width / 2
1455 windowedHeight: root.availableDesktopArea.height / 2
1459 name: "maximizedTopRight"; when: appDelegate.maximizedTopRight && !appDelegate.minimized
1460 extend: "maximizedTopLeft"
1463 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1467 name: "maximizedBottomLeft"; when: appDelegate.maximizedBottomLeft && !appDelegate.minimized
1471 windowedX: root.availableDesktopArea.x
1472 windowedY: root.availableDesktopArea.y + (root.availableDesktopArea.height / 2)
1473 windowedWidth: root.availableDesktopArea.width / 2
1474 windowedHeight: root.availableDesktopArea.height / 2
1478 name: "maximizedBottomRight"; when: appDelegate.maximizedBottomRight && !appDelegate.minimized
1479 extend: "maximizedBottomLeft"
1482 windowedX: root.availableDesktopArea.x + (root.availableDesktopArea.width / 2)
1486 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
1490 windowedX: root.availableDesktopArea.x; windowedY: windowedY
1491 windowedWidth: root.availableDesktopArea.width; windowedHeight: windowedHeight
1495 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
1499 windowedX: windowedX; windowedY: root.availableDesktopArea.y
1500 windowedWidth: windowedWidth; windowedHeight: root.availableDesktopArea.height
1504 name: "minimized"; when: appDelegate.minimized
1507 scale: units.gu(5) / appDelegate.width
1509 visuallyMinimized: true
1510 visuallyMaximized: false
1511 x: -appDelegate.width / 2
1518 from: "staged,stagedWithSideStage"
1519 enabled: appDelegate.animationsEnabled
1520 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
1521 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,opacity,requestedWidth,requestedHeight,scale"; duration: priv.animationDuration }
1524 from: "normal,restored,maximized,maximizedHorizontally,maximizedVertically,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedBottomLeft,maximizedTopRight,maximizedBottomRight";
1525 to: "staged,stagedWithSideStage"
1526 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedX,requestedY,requestedWidth,requestedHeight"; duration: priv.animationDuration}
1530 // DecoratedWindow wants the scaleToPreviewSize set before enabling scaleToPreview
1531 PropertyAction { target: appDelegate; properties: "z,visible" }
1532 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1533 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height"; duration: priv.animationDuration }
1534 UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1535 UbuntuNumberAnimation { target: windowInfoItem; properties: "opacity"; duration: priv.animationDuration }
1538 from: "normal,staged"; to: "stagedWithSideStage"
1539 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,requestedWidth,requestedHeight"; duration: priv.animationDuration }
1542 to: "windowedRightEdge"
1545 windowedRightEdgeMaths.startX = appDelegate.requestedX
1546 windowedRightEdgeMaths.startY = appDelegate.requestedY
1549 var thisRect = { x: appDelegate.windowedX, y: appDelegate.windowedY, width: appDelegate.requestedWidth, height: appDelegate.requestedHeight }
1550 var otherDelegate = appRepeater.itemAt(0);
1551 var otherRect = { x: otherDelegate.windowedX, y: otherDelegate.windowedY, width: otherDelegate.requestedWidth, height: otherDelegate.requestedHeight }
1552 var intersectionRect = MathUtils.intersectionRect(thisRect, otherRect)
1553 var mappedInterSectionRect = appDelegate.mapFromItem(root, intersectionRect.x, intersectionRect.y)
1554 opacityEffect.maskX = mappedInterSectionRect.x
1555 opacityEffect.maskY = mappedInterSectionRect.y
1556 opacityEffect.maskWidth = intersectionRect.width
1557 opacityEffect.maskHeight = intersectionRect.height
1563 from: "stagedRightEdge"; to: "staged"
1564 enabled: rightEdgeDragArea.cancelled // only transition back to state if the gesture was cancelled, in the other cases we play the focusAnimations.
1565 SequentialAnimation {
1567 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,height,width,scale"; duration: priv.animationDuration }
1568 UbuntuNumberAnimation { target: decoratedWindow; properties: "width,height,itemScale,angle,scaleToPreviewProgress"; duration: priv.animationDuration }
1570 // We need to release scaleToPreviewSize at last
1571 PropertyAction { target: decoratedWindow; property: "scaleToPreviewSize" }
1572 PropertyAction { target: appDelegate; property: "visible" }
1576 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1578 SequentialAnimation {
1579 ScriptAction { script: { fakeRectangle.stop(); } }
1580 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1581 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,scale,opacity"; duration: priv.animationDuration }
1582 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1587 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1588 SequentialAnimation {
1589 PropertyAction { target: appDelegate; property: "visuallyMinimized,z" }
1591 UbuntuNumberAnimation { target: appDelegate; properties: "x"; from: -appDelegate.width / 2; duration: priv.animationDuration }
1592 UbuntuNumberAnimation { target: appDelegate; properties: "y,opacity"; duration: priv.animationDuration }
1593 UbuntuNumberAnimation { target: appDelegate; properties: "scale"; from: 0; duration: priv.animationDuration }
1595 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
1599 id: windowedTransition
1600 from: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen,minimized"
1601 to: ",normal,restored,maximized,maximizedLeft,maximizedRight,maximizedTopLeft,maximizedTopRight,maximizedBottomLeft,maximizedBottomRight,maximizedHorizontally,maximizedVertically,fullscreen"
1602 enabled: appDelegate.animationsEnabled
1603 SequentialAnimation {
1604 ScriptAction { script: {
1605 if (appDelegate.visuallyMaximized) visuallyMaximized = false; // maximized before -> going to restored
1608 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
1609 UbuntuNumberAnimation { target: appDelegate; properties: "requestedX,requestedY,windowedX,windowedY,opacity,scale,requestedWidth,requestedHeight,windowedWidth,windowedHeight";
1610 duration: priv.animationDuration }
1611 ScriptAction { script: {
1612 fakeRectangle.stop();
1613 appDelegate.visuallyMaximized = appDelegate.maximized; // reflect the target state
1622 property: "decorationsAlwaysVisible"
1623 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1628 objectName: "windowResizeArea"
1630 anchors.fill: appDelegate
1632 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
1633 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
1636 boundsItem: root.availableDesktopArea
1637 minWidth: units.gu(10)
1638 minHeight: units.gu(10)
1639 borderThickness: units.gu(2)
1644 appDelegate.activate();
1650 objectName: "decoratedWindow"
1651 anchors.left: appDelegate.left
1652 anchors.top: appDelegate.top
1653 application: model.application
1654 surface: model.window.surface
1655 active: model.window.focused
1657 interactive: root.interactive
1659 decorationHeight: priv.windowDecorationHeight
1660 maximizeButtonShown: appDelegate.canBeMaximized
1661 overlayShown: touchControls.overlayShown
1662 width: implicitWidth
1663 height: implicitHeight
1664 highlightSize: windowInfoItem.iconMargin / 2
1665 altDragEnabled: root.mode == "windowed"
1666 boundsItem: root.availableDesktopArea
1668 requestedWidth: appDelegate.requestedWidth
1669 requestedHeight: appDelegate.requestedHeight
1671 property int oldRequestedWidth: -1
1672 property int oldRequestedHeight: -1
1674 onRequestedWidthChanged: oldRequestedWidth = requestedWidth
1675 onRequestedHeightChanged: oldRequestedHeight = requestedHeight
1677 onCloseClicked: { appDelegate.close(); }
1678 onMaximizeClicked: {
1679 if (appDelegate.canBeMaximized) {
1680 appDelegate.anyMaximized ? appDelegate.requestRestore() : appDelegate.requestMaximize();
1683 onMaximizeHorizontallyClicked: {
1684 if (appDelegate.canBeMaximizedHorizontally) {
1685 appDelegate.maximizedHorizontally ? appDelegate.requestRestore() : appDelegate.requestMaximizeHorizontally()
1688 onMaximizeVerticallyClicked: {
1689 if (appDelegate.canBeMaximizedVertically) {
1690 appDelegate.maximizedVertically ? appDelegate.requestRestore() : appDelegate.requestMaximizeVertically()
1693 onMinimizeClicked: { appDelegate.requestMinimize(); }
1694 onDecorationPressed: { appDelegate.activate(); }
1695 onDecorationReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
1697 property real angle: 0
1698 Behavior on angle { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
1699 property real itemScale: 1
1700 Behavior on itemScale { enabled: priv.closingIndex >= 0; UbuntuNumberAnimation {} }
1705 origin.y: decoratedWindow.implicitHeight / 2
1706 xScale: decoratedWindow.itemScale
1707 yScale: decoratedWindow.itemScale
1710 origin { x: 0; y: (decoratedWindow.height / 2) }
1711 axis { x: 0; y: 1; z: 0 }
1712 angle: decoratedWindow.angle
1719 anchors.fill: decoratedWindow
1722 WindowControlsOverlay {
1724 anchors.fill: appDelegate
1726 resizeArea: resizeArea
1729 boundsItem: root.availableDesktopArea
1731 onFakeMaximizeAnimationRequested: if (!appDelegate.maximized) fakeRectangle.maximize(amount, true)
1732 onFakeMaximizeLeftAnimationRequested: if (!appDelegate.maximizedLeft) fakeRectangle.maximizeLeft(amount, true)
1733 onFakeMaximizeRightAnimationRequested: if (!appDelegate.maximizedRight) fakeRectangle.maximizeRight(amount, true)
1734 onFakeMaximizeTopLeftAnimationRequested: if (!appDelegate.maximizedTopLeft) fakeRectangle.maximizeTopLeft(amount, true);
1735 onFakeMaximizeTopRightAnimationRequested: if (!appDelegate.maximizedTopRight) fakeRectangle.maximizeTopRight(amount, true);
1736 onFakeMaximizeBottomLeftAnimationRequested: if (!appDelegate.maximizedBottomLeft) fakeRectangle.maximizeBottomLeft(amount, true);
1737 onFakeMaximizeBottomRightAnimationRequested: if (!appDelegate.maximizedBottomRight) fakeRectangle.maximizeBottomRight(amount, true);
1738 onStopFakeAnimation: fakeRectangle.stop();
1739 onDragReleased: fakeRectangle.visible ? fakeRectangle.commit() : appDelegate.updateRestoredGeometry()
1742 WindowedFullscreenPolicy {
1743 id: windowedFullscreenPolicy
1745 StagedFullscreenPolicy {
1746 id: stagedFullscreenPolicy
1747 active: root.mode == "staged" || root.mode == "stagedWithSideStage"
1748 surface: model.window.surface
1751 SpreadDelegateInputArea {
1753 objectName: "dragArea"
1754 anchors.fill: decoratedWindow
1759 spreadItem.highlightedIndex = index;
1760 if (distance == 0) {
1761 priv.goneToSpread = false;
1765 priv.closingIndex = index
1766 model.window.close();
1772 objectName: "windowInfoItem"
1773 anchors { left: parent.left; top: decoratedWindow.bottom; topMargin: units.gu(1) }
1774 title: model.application.name
1775 iconSource: model.application.icon
1776 height: spreadItem.appInfoHeight
1779 visible: opacity > 0
1781 var nextApp = appRepeater.itemAt(index + 1);
1783 return Math.max(iconHeight, nextApp.x - appDelegate.x - units.gu(1))
1785 return appDelegate.width;
1789 spreadItem.highlightedIndex = index;
1790 priv.goneToSpread = false;
1796 objectName: "closeMouseArea"
1797 anchors { left: parent.left; top: parent.top; leftMargin: -height / 2; topMargin: -height / 2 + spreadMaths.closeIconOffset }
1798 readonly property var mousePos: hoverMouseArea.mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1799 visible: dragArea.distance == 0
1800 && index == spreadItem.highlightedIndex
1801 && mousePos.y < (decoratedWindow.height / 3)
1802 && mousePos.y > -units.gu(4)
1803 && mousePos.x > -units.gu(4)
1804 && mousePos.x < (decoratedWindow.width * 2 / 3)
1809 priv.closingIndex = index;
1810 appDelegate.close();
1814 source: "graphics/window-close.svg"
1815 anchors.fill: closeMouseArea
1816 anchors.margins: units.gu(2)
1817 sourceSize.width: width
1818 sourceSize.height: height
1823 // Group all child windows in this item so that we can fade them out together when going to the spread
1824 // (and fade them in back again when returning from it)
1825 readonly property bool stageOnProperState: root.state === "windowed"
1826 || root.state === "staged"
1827 || root.state === "stagedWithSideStage"
1829 // TODO: Is it worth the extra cost of layering to avoid the opacity artifacts of intersecting children?
1830 // Btw, will involve more than uncommenting the line below as children won't necessarily fit this item's
1831 // geometry. This is just a reference.
1832 //layer.enabled: opacity !== 0.0 && opacity !== 1.0
1834 opacity: stageOnProperState ? 1.0 : 0.0
1835 visible: opacity !== 0.0 // make it transparent to input as well
1836 Behavior on opacity { UbuntuNumberAnimation {} }
1839 id: childWindowRepeater
1840 model: appDelegate.surface ? appDelegate.surface.childSurfaceList : null
1842 delegate: ChildWindowTree {
1843 surface: model.surface
1845 // Account for the displacement caused by window decoration in the top-level surface
1846 // Ie, the top-level surface is not positioned at (0,0) of this ChildWindow's parent (appDelegate)
1847 displacementX: appDelegate.clientAreaItem.x
1848 displacementY: appDelegate.clientAreaItem.y
1850 boundsItem: root.availableDesktopArea
1851 decorationHeight: priv.windowDecorationHeight
1853 z: childWindowRepeater.count - model.index
1857 // some child surface in this tree got focus.
1858 // Ensure we also have it at the top-level hierarchy
1859 appDelegate.claimFocus();
1869 FakeMaximizeDelegate {
1871 target: priv.focusedAppDelegate
1872 leftMargin: root.availableDesktopArea.x
1873 appContainerWidth: appContainer.width
1874 appContainerHeight: appContainer.height
1879 objectName: "hoverMouseArea"
1880 anchors.fill: appContainer
1881 propagateComposedEvents: true
1886 property int scrollAreaWidth: width / 3
1887 property bool progressiveScrollingEnabled: false
1890 mouse.accepted = false
1892 if (hoverMouseArea.pressed) {
1896 // Find the hovered item and mark it active
1897 for (var i = appRepeater.count - 1; i >= 0; i--) {
1898 var appDelegate = appRepeater.itemAt(i);
1899 var mapped = mapToItem(appDelegate, hoverMouseArea.mouseX, hoverMouseArea.mouseY)
1900 var itemUnder = appDelegate.childAt(mapped.x, mapped.y);
1901 if (itemUnder && (itemUnder.objectName === "dragArea" || itemUnder.objectName === "windowInfoItem" || itemUnder.objectName == "closeMouseArea")) {
1902 spreadItem.highlightedIndex = i;
1907 if (floatingFlickable.contentWidth > floatingFlickable.width) {
1908 var margins = floatingFlickable.width * 0.05;
1910 if (!progressiveScrollingEnabled && mouseX < floatingFlickable.width - scrollAreaWidth) {
1911 progressiveScrollingEnabled = true
1914 // do we need to scroll?
1915 if (mouseX < scrollAreaWidth + margins) {
1916 var progress = Math.min(1, (scrollAreaWidth + margins - mouseX) / (scrollAreaWidth - margins));
1917 var contentX = (1 - progress) * (floatingFlickable.contentWidth - floatingFlickable.width)
1918 floatingFlickable.contentX = Math.max(0, Math.min(floatingFlickable.contentX, contentX))
1920 if (mouseX > floatingFlickable.width - scrollAreaWidth && progressiveScrollingEnabled) {
1921 var progress = Math.min(1, (mouseX - (floatingFlickable.width - scrollAreaWidth)) / (scrollAreaWidth - margins))
1922 var contentX = progress * (floatingFlickable.contentWidth - floatingFlickable.width)
1923 floatingFlickable.contentX = Math.min(floatingFlickable.contentWidth - floatingFlickable.width, Math.max(floatingFlickable.contentX, contentX))
1928 onPressed: mouse.accepted = false
1932 id: floatingFlickable
1933 objectName: "spreadFlickable"
1934 anchors.fill: appContainer
1936 contentWidth: spreadItem.spreadTotalWidth
1938 function snap(toIndex) {
1939 var delegate = appRepeater.itemAt(toIndex)
1940 var targetContentX = floatingFlickable.contentWidth / spreadItem.totalItemCount * toIndex;
1941 if (targetContentX - floatingFlickable.contentX > spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) {
1942 var offset = (spreadItem.rightStackXPos - (spreadItem.spreadItemWidth / 2)) - (targetContentX - floatingFlickable.contentX)
1943 snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1944 snapAnimation.start();
1945 } else if (targetContentX - floatingFlickable.contentX < spreadItem.leftStackXPos + units.gu(1)) {
1946 var offset = (spreadItem.leftStackXPos + units.gu(1)) - (targetContentX - floatingFlickable.contentX);
1947 snapAnimation.to = Math.max(0, floatingFlickable.contentX - offset);
1948 snapAnimation.start();
1951 UbuntuNumberAnimation {id: snapAnimation; target: floatingFlickable; property: "contentX"}
1955 id: shortRightEdgeSwipeAnimation
1958 duration: priv.animationDuration
1962 id: rightEdgeDragArea
1963 objectName: "rightEdgeDragArea"
1964 direction: Direction.Leftwards
1965 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1966 width: root.dragAreaWidth
1967 enabled: root.spreadEnabled
1969 property var gesturePoints: []
1970 property bool cancelled: false
1972 property real progress: -touchPosition.x / root.width
1973 onProgressChanged: {
1975 draggedProgress = progress;
1979 property real draggedProgress: 0
1981 onTouchPositionChanged: {
1982 gesturePoints.push(touchPosition.x);
1983 if (gesturePoints.length > 10) {
1984 gesturePoints.splice(0, gesturePoints.length - 10)
1988 onDraggingChanged: {
1990 // A potential edge-drag gesture has started. Start recording it
1993 draggedProgress = 0;
1995 // Ok. The user released. Did he drag far enough to go to full spread?
1996 if (gesturePoints[gesturePoints.length - 1] < -spreadItem.rightEdgeBreakPoint * spreadItem.width ) {
1998 // He dragged far enough, but if the last movement was a flick to the right again, he wants to cancel the spread again.
1999 var oneWayFlickToRight = true;
2000 var smallestX = gesturePoints[0]-1;
2001 for (var i = 0; i < gesturePoints.length; i++) {
2002 if (gesturePoints[i] <= smallestX) {
2003 oneWayFlickToRight = false;
2006 smallestX = gesturePoints[i];
2009 if (!oneWayFlickToRight) {
2010 // Ok, the user made it, let's go to spread!
2011 priv.goneToSpread = true;
2016 // Ok, the user didn't drag far enough to cross the breakPoint
2017 // Find out if it was a one-way movement to the left, in which case we just switch directly to next app.
2018 var oneWayFlick = true;
2019 var smallestX = rightEdgeDragArea.width;
2020 for (var i = 0; i < gesturePoints.length; i++) {
2021 if (gesturePoints[i] >= smallestX) {
2022 oneWayFlick = false;
2025 smallestX = gesturePoints[i];
2028 if (appRepeater.count > 1 &&
2029 (oneWayFlick && rightEdgeDragArea.distance > units.gu(2) || rightEdgeDragArea.distance > spreadItem.rightEdgeBreakPoint * spreadItem.width)) {
2030 var nextStage = appRepeater.itemAt(priv.nextInStack).stage
2031 for (var i = 0; i < appRepeater.count; i++) {
2032 if (i != priv.nextInStack && appRepeater.itemAt(i).stage == nextStage) {
2033 appRepeater.itemAt(i).playHidingAnimation()
2037 appRepeater.itemAt(priv.nextInStack).playFocusAnimation()
2038 if (appRepeater.itemAt(priv.nextInStack).stage == ApplicationInfoInterface.SideStage && !sideStage.shown) {
2052 TabletSideStageTouchGesture {
2054 objectName: "triGestureArea"
2055 anchors.fill: parent
2057 property Item appDelegate
2059 dragComponent: dragComponent
2060 dragComponentProperties: { "appDelegate": appDelegate }
2063 function matchDelegate(obj) { return String(obj.objectName).indexOf("appDelegate") >= 0; }
2065 var delegateAtCenter = Functions.itemAt(appContainer, x, y, matchDelegate);
2066 if (!delegateAtCenter) return;
2068 appDelegate = delegateAtCenter;
2072 if (sideStage.shown) {
2076 priv.updateMainAndSideStageIndexes()
2081 // If we're dragging to the sidestage.
2082 if (!sideStage.shown) {
2090 property Item appDelegate
2092 surface: appDelegate ? appDelegate.surface : null
2094 consumesInput: false
2097 requestedWidth: appDelegate.requestedWidth
2098 requestedHeight: appDelegate.requestedHeight
2101 height: units.gu(40)
2103 Drag.hotSpot.x: width/2
2104 Drag.hotSpot.y: height/2
2105 // only accept opposite stage.
2107 if (!surface) return "Disabled";
2108 if (appDelegate.isDash) return "Disabled";
2110 if (appDelegate.stage === ApplicationInfo.MainStage) {
2111 if (appDelegate.application.supportedOrientations
2112 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {