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: priv.focusedAppDelegate ? priv.focusedAppDelegate.x + units.gu(3) : (normalZ - 1) * units.gu(3)
267 y: priv.focusedAppDelegate ? priv.focusedAppDelegate.y + units.gu(3) : normalZ * units.gu(3)
269 width: decoratedWindow.width
270 height: decoratedWindow.height
274 onShellOrientationAngleChanged: {
275 // at this point decoratedWindow.surfaceOrientationAngle is the old shellOrientationAngle
276 if (application && application.rotatesWindowContents) {
277 if (state == "normal") {
278 var angleDiff = decoratedWindow.surfaceOrientationAngle - shellOrientationAngle;
279 angleDiff = (360 + angleDiff) % 360;
280 if (angleDiff === 90 || angleDiff === 270) {
281 var aux = decoratedWindow.requestedHeight;
282 decoratedWindow.requestedHeight = decoratedWindow.requestedWidth + decoratedWindow.visibleDecorationHeight;
283 decoratedWindow.requestedWidth = aux - decoratedWindow.visibleDecorationHeight;
286 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
288 decoratedWindow.surfaceOrientationAngle = 0;
293 readonly property alias application: decoratedWindow.application
294 readonly property alias minimumWidth: decoratedWindow.minimumWidth
295 readonly property alias minimumHeight: decoratedWindow.minimumHeight
296 readonly property alias maximumWidth: decoratedWindow.maximumWidth
297 readonly property alias maximumHeight: decoratedWindow.maximumHeight
298 readonly property alias widthIncrement: decoratedWindow.widthIncrement
299 readonly property alias heightIncrement: decoratedWindow.heightIncrement
300 property int requestedWidth: -1
301 property int requestedHeight: -1
303 readonly property bool maximized: windowState & WindowStateStorage.WindowStateMaximized
304 readonly property bool maximizedLeft: windowState & WindowStateStorage.WindowStateMaximizedLeft
305 readonly property bool maximizedRight: windowState & WindowStateStorage.WindowStateMaximizedRight
306 readonly property bool maximizedHorizontally: windowState & WindowStateStorage.WindowStateMaximizedHorizontally
307 readonly property bool maximizedVertically: windowState & WindowStateStorage.WindowStateMaximizedVertically
308 readonly property bool minimized: windowState & WindowStateStorage.WindowStateMinimized
309 readonly property alias fullscreen: decoratedWindow.fullscreen
311 property int windowState: WindowStateStorage.WindowStateNormal
312 property bool animationsEnabled: true
313 property alias title: decoratedWindow.title
314 readonly property string appName: model.application ? model.application.name : ""
315 property bool visuallyMaximized: false
316 property bool visuallyMinimized: false
318 readonly property var surface: model.surface
319 readonly property alias resizeArea: resizeArea
321 function claimFocus() {
322 if (spread.state == "altTab") {
325 appDelegate.restore();
328 target: model.surface
329 onFocusRequested: claimFocus();
332 target: model.application
334 if (!model.surface) {
335 // when an app has no surfaces, we assume there's only one entry representing it:
339 // if the application has surfaces, focus request should be at surface-level.
345 if (appRepeater.startingUp)
349 // If we're orphan (!parent) it means this stage is no longer the current one
350 // and will be deleted shortly. So we should no longer have a say over the model
352 topLevelSurfaceList.raiseId(model.id);
355 priv.focusedAppDelegate = appDelegate;
356 } else if (!focus && priv.focusedAppDelegate === appDelegate) {
357 priv.focusedAppDelegate = null;
358 // FIXME: No idea why the Binding{} doens't update when focusedAppDelegate turns null
359 MirFocusController.focusedSurface = null;
362 Component.onCompleted: {
363 if (application && application.rotatesWindowContents) {
364 decoratedWindow.surfaceOrientationAngle = shellOrientationAngle;
366 decoratedWindow.surfaceOrientationAngle = 0;
369 // NB: We're differentiating if this delegate was created in response to a new entry in the model
370 // or if the Repeater is just populating itself with delegates to match the model it received.
371 if (!appRepeater.startingUp) {
372 // a top level window is always the focused one when it first appears, unfocusing
373 // any preexisting one
377 Component.onDestruction: {
379 // This stage is about to be destroyed. Don't mess up with the model at this point
383 if (visuallyMaximized) {
384 priv.updateForegroundMaximizedApp();
388 // focus some other window
389 for (var i = 0; i < appRepeater.count; i++) {
390 var appDelegate = appRepeater.itemAt(i);
391 if (appDelegate && !appDelegate.minimized && i != index) {
392 appDelegate.focus = true;
399 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
403 && !greeter.fullyShown
404 && (priv.foregroundMaximizedAppDelegate === null || priv.foregroundMaximizedAppDelegate.normalZ <= z)
406 || decoratedWindow.fullscreen
407 || (spread.state == "altTab" && index === spread.highlightedIndex)
410 model.surface.close();
413 function maximize(animated) {
414 animationsEnabled = (animated === undefined) || animated;
415 windowState = WindowStateStorage.WindowStateMaximized;
417 function maximizeLeft(animated) {
418 animationsEnabled = (animated === undefined) || animated;
419 windowState = WindowStateStorage.WindowStateMaximizedLeft;
421 function maximizeRight(animated) {
422 animationsEnabled = (animated === undefined) || animated;
423 windowState = WindowStateStorage.WindowStateMaximizedRight;
425 function maximizeHorizontally(animated) {
426 animationsEnabled = (animated === undefined) || animated;
427 windowState = WindowStateStorage.WindowStateMaximizedHorizontally;
429 function maximizeVertically(animated) {
430 animationsEnabled = (animated === undefined) || animated;
431 windowState = WindowStateStorage.WindowStateMaximizedVertically;
433 function minimize(animated) {
434 animationsEnabled = (animated === undefined) || animated;
435 windowState |= WindowStateStorage.WindowStateMinimized; // add the minimized bit
437 function restoreFromMaximized(animated) {
438 animationsEnabled = (animated === undefined) || animated;
439 windowState = WindowStateStorage.WindowStateNormal;
441 function restore(animated) {
442 animationsEnabled = (animated === undefined) || animated;
443 windowState &= ~WindowStateStorage.WindowStateMinimized; // clear the minimized bit
446 else if (maximizedLeft)
448 else if (maximizedRight)
450 else if (maximizedHorizontally)
451 maximizeHorizontally();
452 else if (maximizedVertically)
453 maximizeVertically();
458 function playFocusAnimation() {
459 focusAnimation.start()
462 UbuntuNumberAnimation {
468 duration: UbuntuAnimation.SnapDuration
473 name: "fullscreen"; when: decoratedWindow.fullscreen && !appDelegate.minimized
476 x: rotation == 0 ? 0 : (parent.width - width) / 2 + (shellOrientationAngle == 90 ? -PanelState.panelHeight : PanelState.panelHeight)
477 y: rotation == 0 ? -PanelState.panelHeight : (parent.height - height) / 2
478 requestedWidth: appContainer.width;
479 requestedHeight: appContainer.height;
484 when: appDelegate.windowState == WindowStateStorage.WindowStateNormal
487 visuallyMinimized: false;
488 visuallyMaximized: false
492 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
497 visuallyMinimized: false;
498 visuallyMaximized: true
501 target: decoratedWindow
502 requestedWidth: appContainer.width - root.leftMargin;
503 requestedHeight: appContainer.height;
507 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
511 y: PanelState.panelHeight
514 target: decoratedWindow
515 requestedWidth: (appContainer.width - root.leftMargin)/2
516 requestedHeight: appContainer.height - PanelState.panelHeight
520 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
523 x: (appContainer.width + root.leftMargin)/2
524 y: PanelState.panelHeight
527 target: decoratedWindow
528 requestedWidth: (appContainer.width - root.leftMargin)/2
529 requestedHeight: appContainer.height - PanelState.panelHeight
533 name: "maximizedHorizontally"; when: appDelegate.maximizedHorizontally && !appDelegate.minimized
534 PropertyChanges { target: appDelegate; x: root.leftMargin }
535 PropertyChanges { target: decoratedWindow; requestedWidth: appContainer.width - root.leftMargin }
538 name: "maximizedVertically"; when: appDelegate.maximizedVertically && !appDelegate.minimized
539 PropertyChanges { target: appDelegate; y: PanelState.panelHeight }
540 PropertyChanges { target: decoratedWindow; requestedHeight: appContainer.height - PanelState.panelHeight }
543 name: "minimized"; when: appDelegate.minimized
546 x: -appDelegate.width / 2;
547 scale: units.gu(5) / appDelegate.width;
549 visuallyMinimized: true;
550 visuallyMaximized: false
557 enabled: appDelegate.animationsEnabled
558 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
559 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
560 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
564 enabled: appDelegate.animationsEnabled
565 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
566 SequentialAnimation {
568 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
569 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
571 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
574 if (appDelegate.minimized) {
575 appDelegate.focus = false;
583 to: "*" //maximized and fullscreen
584 enabled: appDelegate.animationsEnabled
585 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
586 SequentialAnimation {
588 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
589 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
591 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
600 value: topLevelSurfaceList.count + 1
601 when: index == spread.highlightedIndex && spread.ready
606 property: "buttonsAlwaysVisible"
607 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
612 objectName: "windowResizeArea"
614 // workaround so that it chooses the correct resize borders when you drag from a corner ResizeGrip
615 anchors.margins: touchControls.overlayShown ? borderThickness/2 : -borderThickness
618 minWidth: units.gu(10)
619 minHeight: units.gu(10)
620 borderThickness: units.gu(2)
621 windowId: model.application.appId // FIXME: Change this to point to windowId once we have such a thing
622 screenWidth: appContainer.width
623 screenHeight: appContainer.height
624 leftMargin: root.leftMargin
626 onPressed: { appDelegate.focus = true; }
628 Component.onCompleted: {
632 property bool saveStateOnDestruction: true
635 onStageAboutToBeUnloaded: {
636 resizeArea.saveWindowState();
637 resizeArea.saveStateOnDestruction = false;
638 fullscreenPolicy.active = false;
641 Component.onDestruction: {
642 if (saveStateOnDestruction) {
650 objectName: "decoratedWindow"
651 anchors.left: appDelegate.left
652 anchors.top: appDelegate.top
653 application: model.application
654 surface: model.surface
655 active: appDelegate.focus
657 overlayShown: touchControls.overlayShown
659 requestedWidth: appDelegate.requestedWidth
660 requestedHeight: appDelegate.requestedHeight
662 onCloseClicked: { appDelegate.close(); }
663 onMaximizeClicked: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
664 || appDelegate.maximizedHorizontally || appDelegate.maximizedVertically
665 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
666 onMaximizeHorizontallyClicked: appDelegate.maximizedHorizontally ? appDelegate.restoreFromMaximized() : appDelegate.maximizeHorizontally()
667 onMaximizeVerticallyClicked: appDelegate.maximizedVertically ? appDelegate.restoreFromMaximized() : appDelegate.maximizeVertically()
668 onMinimizeClicked: appDelegate.minimize()
669 onDecorationPressed: { appDelegate.focus = true; }
672 WindowControlsOverlay {
674 anchors.fill: appDelegate
678 WindowedFullscreenPolicy {
681 surface: model.surface
690 // NB: it does its own positioning according to the specified edge
693 onPassed: { spread.show(); }
694 material: Component {
700 anchors.centerIn: parent
702 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
703 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
711 direction: Direction.Leftwards
712 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
714 onDraggingChanged: { if (dragging) { spread.show(); } }
720 anchors.fill: appContainer
721 workspace: appContainer
722 focus: state == "altTab"
723 altTabPressed: root.altTabPressed
725 onPlayFocusAnimation: {
726 appRepeater.itemAt(index).playFocusAnimation();