2 * Copyright (C) 2014-2016 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
29 paintBackground: false
31 // functions to be called from outside
32 function updateFocusedAppOrientation() { /* TODO */ }
33 function updateFocusedAppOrientationAnimated() { /* TODO */}
34 function pushRightEdge(amount) {
35 if (spread.state === "") {
36 edgeBarrier.push(amount);
40 // Used by TutorialRight
41 property bool spreadShown: spread.state == "altTab"
43 mainApp: priv.focusedAppDelegate ? priv.focusedAppDelegate.application : null
45 // application windows never rotate independently
46 mainAppWindowOrientationAngle: shellOrientationAngle
48 orientationChangesEnabled: true
51 id: closeWindowShortcut
52 shortcut: Qt.AltModifier|Qt.Key_F4
53 onTriggered: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
54 active: priv.focusedAppDelegate !== null
58 id: showSpreadShortcut
59 shortcut: Qt.MetaModifier|Qt.Key_W
60 onTriggered: spread.state = "altTab"
64 id: minimizeAllShortcut
65 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
66 onTriggered: priv.minimizeAllWindows()
70 id: maximizeWindowShortcut
71 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
72 onTriggered: priv.focusedAppDelegate.maximize()
73 active: priv.focusedAppDelegate !== null
77 id: maximizeWindowLeftShortcut
78 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
79 onTriggered: priv.focusedAppDelegate.maximizeLeft()
80 active: priv.focusedAppDelegate !== null
84 id: maximizeWindowRightShortcut
85 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
86 onTriggered: priv.focusedAppDelegate.maximizeRight()
87 active: priv.focusedAppDelegate !== null
91 id: minimizeRestoreShortcut
92 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
93 onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight ||
94 priv.focusedAppDelegate.maximizedHorizontally || priv.focusedAppDelegate.maximizedVertically
95 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
96 active: priv.focusedAppDelegate !== null
100 shortcut: Qt.AltModifier|Qt.Key_Print
101 onTriggered: root.itemSnapshotRequested(priv.focusedAppDelegate)
102 active: priv.focusedAppDelegate !== null
106 target: root.topLevelSurfaceList
108 if (spread.state == "altTab") {
116 objectName: "DesktopStagePrivate"
118 property var focusedAppDelegate: null
119 onFocusedAppDelegateChanged: {
120 if (spread.state == "altTab") {
125 property var foregroundMaximizedAppDelegate: null // for stuff like drop shadow and focusing maximized app by clicking panel
127 function updateForegroundMaximizedApp() {
129 for (var i = 0; i < appRepeater.count && !found; i++) {
130 var item = appRepeater.itemAt(i);
131 if (item && item.visuallyMaximized) {
132 foregroundMaximizedAppDelegate = item;
137 foregroundMaximizedAppDelegate = null;
141 function minimizeAllWindows() {
142 for (var i = 0; i < appRepeater.count; i++) {
143 var appDelegate = appRepeater.itemAt(i);
144 if (appDelegate && !appDelegate.minimized) {
145 appDelegate.minimize();
150 function focusNext() {
151 for (var i = 0; i < appRepeater.count; i++) {
152 var appDelegate = appRepeater.itemAt(i);
153 if (appDelegate && !appDelegate.minimized) {
154 appDelegate.focus = true;
163 onCloseClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.close(); } }
164 onMinimizeClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.minimize(); } }
165 onRestoreClicked: { if (priv.focusedAppDelegate) { priv.focusedAppDelegate.restoreFromMaximized(); } }
166 onFocusMaximizedApp: {
167 if (priv.foregroundMaximizedAppDelegate) {
168 priv.foregroundMaximizedAppDelegate.focus = true;
175 property: "buttonsVisible"
176 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
177 && spread.state == ""
184 if (priv.focusedAppDelegate !== null && spread.state == "") {
185 if (priv.focusedAppDelegate.maximized)
186 return priv.focusedAppDelegate.title
188 return priv.focusedAppDelegate.appName
192 when: priv.focusedAppDelegate
197 property: "dropShadow"
198 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppDelegate !== null
203 property: "closeButtonShown"
204 value: priv.focusedAppDelegate && priv.focusedAppDelegate.maximized && priv.focusedAppDelegate.application.appId !== "unity8-dash"
207 Component.onDestruction: {
208 PanelState.title = "";
209 PanelState.buttonsVisible = false;
210 PanelState.dropShadow = false;
214 model: root.applicationManager
216 target: model.application
217 property: "requestedState"
219 // TODO: figure out some lifecycle policy, like suspending minimized apps
220 // if running on a tablet or something.
221 // TODO: If the device has a dozen suspended apps because it was running
222 // in staged mode, when it switches to Windowed mode it will suddenly
223 // resume all those apps at once. We might want to avoid that.
224 value: ApplicationInfoInterface.RequestedRunning // Always running for now
229 target: MirFocusController
230 property: "focusedSurface"
231 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
232 when: !appRepeater.startingUp && root.parent
237 objectName: "appContainer"
239 focus: spread.state !== "altTab"
244 source: root.background
245 sourceSize { height: root.height; width: root.width }
246 fillMode: Image.PreserveAspectCrop
249 TopLevelSurfaceRepeater {
251 model: topLevelSurfaceList
252 objectName: "appRepeater"
254 delegate: FocusScope {
256 objectName: "appDelegate_" + model.id
257 // z might be overriden in some cases by effects, but we need z ordering
258 // to calculate occlusion detection
259 property int normalZ: topLevelSurfaceList.count - index
261 if (visuallyMaximized) {
262 priv.updateForegroundMaximizedApp();
266 x: requestedX // may be overridden in some states. Do not directly write to this.
267 y: requestedY // may be overridden in some states. Do not directly write to this.
268 property real requestedX: priv.focusedAppDelegate ? priv.focusedAppDelegate.x + units.gu(3) : (normalZ - 1) * units.gu(3)
269 property real requestedY: priv.focusedAppDelegate ? priv.focusedAppDelegate.y + units.gu(3) : normalZ * units.gu(3)
274 value: appDelegate.requestedY -
275 Math.min(appDelegate.requestedY - PanelState.panelHeight,
276 Math.max(0, UbuntuKeyboardInfo.height - (appContainer.height - (appDelegate.requestedY + appDelegate.height))))
277 when: root.oskEnabled && appDelegate.focus && appDelegate.state == "normal"
278 && SurfaceManager.inputMethodSurface
279 && SurfaceManager.inputMethodSurface.state != Mir.HiddenState
280 && SurfaceManager.inputMethodSurface.state != Mir.MinimizedState
284 width: decoratedWindow.width
285 height: decoratedWindow.height
289 onShellOrientationAngleChanged: {
290 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
291 if (application && application.rotatesWindowContents) {
292 if (state == "normal") {
293 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
294 angleDiff = (360 + angleDiff) % 360;
295 if (angleDiff === 90 || angleDiff === 270) {
296 var aux = decoratedWindow.requestedHeight;
297 decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.visibleDecorationHeight;
298 decoratedWindow.requestedWidth = aux - decoratedWindow.visibleDecorationHeight;
301 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
303 decoratedWindow.surfaceOrientationAngle = 0;
308 readonly property alias application: decoratedWindow.application
309 readonly property alias minimumWidth: decoratedWindow.minimumWidth
310 readonly property alias minimumHeight: decoratedWindow.minimumHeight
311 readonly property alias maximumWidth: decoratedWindow.maximumWidth
312 readonly property alias maximumHeight: decoratedWindow.maximumHeight
313 readonly property alias widthIncrement: decoratedWindow.widthIncrement
314 readonly property alias heightIncrement: decoratedWindow.heightIncrement
315 property int requestedWidth: -1
316 property int requestedHeight: -1
318 readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
319 readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
320 readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
321 readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
322 readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
323 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
324 readonly property alias fullscreen: decoratedWindow.fullscreen
326 property int windowState: WindowStateStorage.WindowStateNormal
327 property bool animationsEnabled: true
328 property alias title: decoratedWindow.title
329 readonly property string appName: model.application ? model.application.name : ""
330 property bool visuallyMaximized: false
331 property bool visuallyMinimized: false
333 readonly property var surface: model.surface
334 readonly property alias resizeArea: resizeArea
336 function claimFocus() {
337 if (spread.state == "altTab") {
340 appDelegate.restore();
343 target: model.surface
344 onFocusRequested: claimFocus();
347 target: model.application
349 if (!model.surface) {
350 // when an app has no surfaces, we assume there's only one entry representing it:
354 // if the application has surfaces, focus request should be at surface-level.
360 if (appRepeater.startingUp)
364 // If we're orphan (!parent) it means this stage is no longer the current one
365 // and will be deleted shortly. So we should no longer have a say over the model
367 topLevelSurfaceList.raiseId(model.id);
370 priv.focusedAppDelegate = appDelegate;
371 } else if (!focus && priv.focusedAppDelegate === appDelegate) {
372 priv.focusedAppDelegate = null;
373 // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
374 MirFocusController.focusedSurface = null;
377 Component.onCompleted: {
378 if (application && application.rotatesWindowContents) {
379 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
381 decoratedWindow.surfaceOrientationAngle = 0;
384 // NB: We're differentiating if this delegate was created in response to a new entry in the model
385 // or if the Repeater is just populating itself with delegates to match the model it received.
386 if (!appRepeater.startingUp) {
387 // a top level window is always the focused one when it first appears, unfocusing
388 // any preexisting one
392 Component.onDestruction: {
394 // This stage is about to be destroyed. Don't mess up with the model at this point
398 if (visuallyMaximized) {
399 priv.updateForegroundMaximizedApp();
403 // focus some other window
404 for (var i = 0; i < appRepeater.count; i++) {
405 var appDelegate = appRepeater.itemAt(i);
406 if (appDelegate && !appDelegate.minimized && i != index) {
407 appDelegate.focus = true;
414 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
418 && !greeter.fullyShown
419 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
421 || decoratedWindow.fullscreen
422 || (spread.state == "altTab" && index === spread.highlightedIndex)
425 model.surface.close();
428 function maximize(animated) {
429 animationsEnabled = (animated === undefined) || animated;
430 windowState = WindowStateStorage.WindowStateMaximized;
432 function maximizeLeft(animated) {
433 animationsEnabled = (animated === undefined) || animated;
434 windowState = WindowStateStorage.WindowStateMaximizedLeft;
436 function maximizeRight(animated) {
437 animationsEnabled = (animated === undefined) || animated;
438 windowState = WindowStateStorage.WindowStateMaximizedRight;
440 function maximizeHorizontally(animated) {
441 animationsEnabled = (animated === undefined) || animated;
442 windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
444 function maximizeVertically(animated) {
445 animationsEnabled = (animated === undefined) || animated;
446 windowState = WindowStateStorage.WindowStateMaximizedVertically;
448 function minimize(animated) {
449 animationsEnabled = (animated === undefined) || animated;
450 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
452 function restoreFromMaximized(animated) {
453 animationsEnabled = (animated === undefined) || animated;
454 windowState = WindowStateStorage.WindowStateNormal;
456 function restore(animated) {
457 animationsEnabled = (animated === undefined) || animated;
458 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
461 else if (maximizedLeft)
463 else if (maximizedRight)
465 else if (maximizedHorizontally)
466 maximizeHorizontally();
467 else if (maximizedVertically)
468 maximizeVertically();
473 function playFocusAnimation() {
474 focusAnimation.start()
477 UbuntuNumberAnimation {
483 duration: UbuntuAnimation.SnapDuration
488 name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
491 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
492 y: rotation == 0 ? -PanelState.panelHeight : (parent.height - height) / 2
493 requestedWidth: appContainer.width;
494 requestedHeight: appContainer.height;
499 when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
502 visuallyMinimized: false;
503 visuallyMaximized: false
507 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
512 visuallyMinimized: false;
513 visuallyMaximized: true
516 target: decoratedWindow
517 requestedWidth: appContainer.width - root.leftMargin;
518 requestedHeight: appContainer.height;
522 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
526 y: PanelState.panelHeight
529 target: decoratedWindow
530 requestedWidth: (appContainer.width - root.leftMargin)/2
531 requestedHeight: appContainer.height - PanelState.panelHeight
535 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
538 x: (appContainer.width + root.leftMargin)/2
539 y: PanelState.panelHeight
542 target: decoratedWindow
543 requestedWidth: (appContainer.width - root.leftMargin)/2
544 requestedHeight: appContainer.height - PanelState.panelHeight
548 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
549 PropertyChanges { target: appDelegate; x: root.leftMargin }
550 PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
553 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
554 PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
555 PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
558 name: "minimized"; when: appDelegate.minimized
561 x: -appDelegate.width / 2;
562 scale: units.gu(5) / appDelegate.width;
564 visuallyMinimized: true;
565 visuallyMaximized: false
572 enabled: appDelegate.animationsEnabled
573 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
574 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
575 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
579 enabled: appDelegate.animationsEnabled
580 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
581 SequentialAnimation {
583 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
584 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
586 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
589 if (appDelegate.minimized) {
590 appDelegate.focus = false;
598 to: "*" //maximized and fullscreen
599 enabled: appDelegate.animationsEnabled
600 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
601 SequentialAnimation {
603 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
604 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
606 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
615 value: topLevelSurfaceList.count + 1
616 when: index == spread.highlightedIndex && spread.ready
621 property: "buttonsAlwaysVisible"
622 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
627 objectName: "windowResizeArea"
629 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
630 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
633 minWidth: units.gu(10)
634 minHeight: units.gu(10)
635 borderThickness: units.gu(2)
636 windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
637 screenWidth: appContainer.width
638 screenHeight: appContainer.height
639 leftMargin: root.leftMargin
641 onPressed: { appDelegate.focus = true; }
643 Component.onCompleted: {
647 property bool saveStateOnDestruction: true
650 onStageAboutToBeUnloaded: {
651 resizeArea.saveWindowState();
652 resizeArea.saveStateOnDestruction = false;
653 fullscreenPolicy.active = false;
656 Component.onDestruction: {
657 if (saveStateOnDestruction) {
665 objectName: "decoratedWindow"
666 anchors.left: appDelegate.left
667 anchors.top: appDelegate.top
668 application: model.application
669 surface: model.surface
670 active: appDelegate.focus
672 maximizeButtonShown: (maximumWidth == 0 || maximumWidth >= appContainer.width) &&
673 (maximumHeight == 0 || maximumHeight >= appContainer.height)
674 overlayShown: touchControls.overlayShown
676 requestedWidth: appDelegate.requestedWidth
677 requestedHeight: appDelegate.requestedHeight
679 onCloseClicked: { appDelegate.close(); }
680 onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
681 || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
682 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
683 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
684 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
685 onMinimizeClicked: appDelegate.minimize()
686 onDecorationPressed: { appDelegate.focus = true; }
689 WindowControlsOverlay {
691 anchors.fill: appDelegate
695 WindowedFullscreenPolicy {
698 surface: model.surface
707 // NB: it does its own positioning according to the specified edge
710 onPassed: { spread.show(); }
711 material: Component {
717 anchors.centerIn: parent
719 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
720 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
728 direction: Direction.Leftwards
729 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
731 onDraggingChanged: { if (dragging) { spread.show(); } }
737 anchors.fill: appContainer
738 workspace: appContainer
739 focus: state == "altTab"
740 altTabPressed: root.altTabPressed
742 onPlayFocusAnimation: {
743 appRepeater.itemAt(index).playFocusAnimation();