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
30 // functions to be called from outside
31 function updateFocusedAppOrientation() { /* TODO */ }
32 function updateFocusedAppOrientationAnimated() { /* TODO */}
33 function pushRightEdge(amount) {
34 if (spread.state === "") {
35 edgeBarrier.push(amount);
39 // Used by TutorialRight
40 property bool spreadShown: spread.state == "altTab"
42 mainApp: ApplicationManager.focusedApplicationId
43 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
46 mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
48 // application windows never rotate independently
49 mainAppWindowOrientationAngle: shellOrientationAngle
51 orientationChangesEnabled: true
54 target: ApplicationManager
56 if (spread.state == "altTab") {
60 ApplicationManager.focusApplication(appId);
63 onApplicationRemoved: {
68 var appIndex = priv.indexOf(appId);
69 var appDelegate = appRepeater.itemAt(appIndex);
70 appDelegate.restore();
72 if (spread.state == "altTab") {
79 id: closeWindowShortcut
80 shortcut: Qt.AltModifier|Qt.Key_F4
81 onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
82 active: priv.focusedAppId !== ""
86 id: showSpreadShortcut
87 shortcut: Qt.MetaModifier|Qt.Key_W
88 onTriggered: spread.state = "altTab"
92 id: minimizeAllShortcut
93 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
94 onTriggered: priv.minimizeAllWindows()
98 id: maximizeWindowShortcut
99 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
100 onTriggered: priv.focusedAppDelegate.maximize()
101 active: priv.focusedAppDelegate !== null
105 id: maximizeWindowLeftShortcut
106 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
107 onTriggered: priv.focusedAppDelegate.maximizeLeft()
108 active: priv.focusedAppDelegate !== null
112 id: maximizeWindowRightShortcut
113 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
114 onTriggered: priv.focusedAppDelegate.maximizeRight()
115 active: priv.focusedAppDelegate !== null
119 id: minimizeRestoreShortcut
120 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
121 onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
122 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
123 active: priv.focusedAppDelegate !== null
129 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
130 readonly property var focusedAppDelegate: {
131 var index = indexOf(focusedAppId);
132 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
134 onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
136 property int foregroundMaximizedAppZ: -1
137 property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
139 function updateForegroundMaximizedApp() {
142 for (var i = appRepeater.count - 1; i >= 0; i--) {
143 var item = appRepeater.itemAt(i);
144 if (item && item.visuallyMaximized) {
146 tmp = Math.max(tmp, item.normalZ);
149 foregroundMaximizedAppZ = tmp;
150 foregroundMaximizedAppIndex = tmpAppId;
153 function indexOf(appId) {
154 for (var i = 0; i < ApplicationManager.count; i++) {
155 if (ApplicationManager.get(i).appId == appId) {
162 function minimizeAllWindows() {
163 for (var i = 0; i < appRepeater.count; i++) {
164 var appDelegate = appRepeater.itemAt(i);
165 if (appDelegate && !appDelegate.minimized) {
166 appDelegate.minimize();
170 ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
173 function focusNext() {
174 ApplicationManager.unfocusCurrentApplication();
175 for (var i = 0; i < appRepeater.count; i++) {
176 var appDelegate = appRepeater.itemAt(i);
177 if (appDelegate && !appDelegate.minimized) {
178 ApplicationManager.focusApplication(appDelegate.appId);
188 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
190 onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
191 onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
192 && priv.focusedAppDelegate.restoreFromMaximized();
193 onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
194 ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
200 property: "buttonsVisible"
201 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
202 && spread.state == ""
209 if (priv.focusedAppDelegate !== null && spread.state == "") {
210 if (priv.focusedAppDelegate.maximized)
211 return priv.focusedAppDelegate.title
213 return priv.focusedAppDelegate.appName
217 when: priv.focusedAppDelegate
222 property: "dropShadow"
223 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1
226 Component.onDestruction: {
227 PanelState.title = "";
228 PanelState.buttonsVisible = false;
229 PanelState.dropShadow = false;
235 objectName: "appContainer"
237 focus: spread.state !== "altTab"
242 source: root.background
243 sourceSize { height: root.height; width: root.width }
244 fillMode: Image.PreserveAspectCrop
249 model: ApplicationManager
250 objectName: "appRepeater"
252 delegate: FocusScope {
254 objectName: "appDelegate_" + appId
255 // z might be overriden in some cases by effects, but we need z ordering
256 // to calculate occlusion detection
257 property int normalZ: ApplicationManager.count - index
259 y: PanelState.panelHeight
260 focus: appId === priv.focusedAppId
261 width: decoratedWindow.width
262 height: decoratedWindow.height
263 property int requestedWidth: -1
264 property int requestedHeight: -1
265 property alias minimumWidth: decoratedWindow.minimumWidth
266 property alias minimumHeight: decoratedWindow.minimumHeight
267 property alias maximumWidth: decoratedWindow.maximumWidth
268 property alias maximumHeight: decoratedWindow.maximumHeight
269 property alias widthIncrement: decoratedWindow.widthIncrement
270 property alias heightIncrement: decoratedWindow.heightIncrement
273 id: appDelegatePrivate
274 property bool maximized: false
275 property bool maximizedLeft: false
276 property bool maximizedRight: false
277 property bool minimized: false
279 readonly property alias maximized: appDelegatePrivate.maximized
280 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
281 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
282 readonly property alias minimized: appDelegatePrivate.minimized
283 readonly property alias fullscreen: decoratedWindow.fullscreen
285 readonly property string appId: model.appId
286 property bool animationsEnabled: true
287 property alias title: decoratedWindow.title
288 readonly property string appName: model.name
289 property bool visuallyMaximized: false
290 property bool visuallyMinimized: false
292 readonly property alias appWindow: decoratedWindow.window
295 if (focus && ApplicationManager.focusedApplicationId !== appId) {
296 ApplicationManager.focusApplication(appId);
300 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
302 visible: !visuallyMinimized &&
303 !greeter.fullyShown &&
304 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
305 decoratedWindow.fullscreen ||
306 (spread.state == "altTab" && index === spread.highlightedIndex)
309 target: ApplicationManager.get(index)
310 property: "requestedState"
311 // TODO: figure out some lifecycle policy, like suspending minimized apps
312 // if running on a tablet or something.
313 // TODO: If the device has a dozen suspended apps because it was running
314 // in staged mode, when it switches to Windowed mode it will suddenly
315 // resume all those apps at once. We might want to avoid that.
316 value: ApplicationInfoInterface.RequestedRunning // Always running for now
319 function maximize(animated) {
320 animationsEnabled = (animated === undefined) || animated;
321 appDelegatePrivate.minimized = false;
322 appDelegatePrivate.maximized = true;
323 appDelegatePrivate.maximizedLeft = false;
324 appDelegatePrivate.maximizedRight = false;
326 function maximizeLeft() {
327 appDelegatePrivate.minimized = false;
328 appDelegatePrivate.maximized = false;
329 appDelegatePrivate.maximizedLeft = true;
330 appDelegatePrivate.maximizedRight = false;
332 function maximizeRight() {
333 appDelegatePrivate.minimized = false;
334 appDelegatePrivate.maximized = false;
335 appDelegatePrivate.maximizedLeft = false;
336 appDelegatePrivate.maximizedRight = true;
338 function minimize(animated) {
339 animationsEnabled = (animated === undefined) || animated;
340 appDelegatePrivate.minimized = true;
342 function restoreFromMaximized(animated) {
343 animationsEnabled = (animated === undefined) || animated;
344 appDelegatePrivate.minimized = false;
345 appDelegatePrivate.maximized = false;
346 appDelegatePrivate.maximizedLeft = false;
347 appDelegatePrivate.maximizedRight = false;
349 function restore(animated) {
350 animationsEnabled = (animated === undefined) || animated;
351 appDelegatePrivate.minimized = false;
354 else if (maximizedLeft)
356 else if (maximizedRight)
358 ApplicationManager.focusApplication(appId);
361 function playFocusAnimation() {
362 focusAnimation.start()
365 UbuntuNumberAnimation {
371 duration: UbuntuAnimation.SnapDuration
376 name: "fullscreen"; when: decoratedWindow.fullscreen
380 y: -PanelState.panelHeight
381 requestedWidth: appContainer.width;
382 requestedHeight: appContainer.height;
387 when: !appDelegate.maximized && !appDelegate.minimized
388 && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
391 visuallyMinimized: false;
392 visuallyMaximized: false
396 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
401 visuallyMinimized: false;
402 visuallyMaximized: true
405 target: decoratedWindow
406 requestedWidth: appContainer.width - root.leftMargin;
407 requestedHeight: appContainer.height;
411 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
415 y: PanelState.panelHeight
418 target: decoratedWindow
419 requestedWidth: (appContainer.width - root.leftMargin)/2
420 requestedHeight: appContainer.height - PanelState.panelHeight
424 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
427 x: (appContainer.width + root.leftMargin)/2
428 y: PanelState.panelHeight
431 target: decoratedWindow
432 requestedWidth: (appContainer.width - root.leftMargin)/2
433 requestedHeight: appContainer.height - PanelState.panelHeight
437 name: "minimized"; when: appDelegate.minimized
440 x: -appDelegate.width / 2;
441 scale: units.gu(5) / appDelegate.width;
443 visuallyMinimized: true;
444 visuallyMaximized: false
451 enabled: appDelegate.animationsEnabled
452 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
453 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
454 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
458 enabled: appDelegate.animationsEnabled
459 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
460 SequentialAnimation {
462 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
463 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
465 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
468 if (appDelegate.minimized) {
476 to: "*" //maximized and fullscreen
477 enabled: appDelegate.animationsEnabled
478 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
479 SequentialAnimation {
481 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
482 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
484 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
493 value: ApplicationManager.count + 1
494 when: index == spread.highlightedIndex && spread.ready
499 objectName: "windowResizeArea"
501 minWidth: units.gu(10)
502 minHeight: units.gu(10)
503 borderThickness: units.gu(2)
504 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
505 screenWidth: appContainer.width
506 screenHeight: appContainer.height
507 leftMargin: root.leftMargin
509 onPressed: { ApplicationManager.focusApplication(model.appId) }
511 Component.onCompleted: {
515 property bool saveStateOnDestruction: true
518 onStageAboutToBeUnloaded: {
519 resizeArea.saveWindowState();
520 resizeArea.saveStateOnDestruction = false;
521 fullscreenPolicy.active = false;
524 Component.onDestruction: {
525 if (saveStateOnDestruction) {
533 objectName: "decoratedWindow"
534 anchors.left: appDelegate.left
535 anchors.top: appDelegate.top
536 application: ApplicationManager.get(index)
537 active: ApplicationManager.focusedApplicationId === model.appId
540 requestedWidth: appDelegate.requestedWidth
541 requestedHeight: appDelegate.requestedHeight
543 onClose: ApplicationManager.stopApplication(model.appId)
544 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
545 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
546 onMinimize: appDelegate.minimize()
547 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
550 WindowedFullscreenPolicy {
553 application: decoratedWindow.application
562 // NB: it does its own positioning according to the specified edge
565 onPassed: { spread.show(); }
566 material: Component {
572 anchors.centerIn: parent
574 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
575 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
582 DirectionalDragArea {
583 direction: Direction.Leftwards
584 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
586 onDraggingChanged: { if (dragging) { spread.show(); } }
592 anchors.fill: appContainer
593 workspace: appContainer
594 focus: state == "altTab"
595 altTabPressed: root.altTabPressed
597 onPlayFocusAnimation: {
598 appRepeater.itemAt(index).playFocusAnimation();