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 mainApp: ApplicationManager.focusedApplicationId
40 ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
43 mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
45 // application windows never rotate independently
46 mainAppWindowOrientationAngle: shellOrientationAngle
48 orientationChangesEnabled: true
51 target: ApplicationManager
53 if (spread.state == "altTab") {
57 ApplicationManager.focusApplication(appId);
60 onApplicationRemoved: {
65 var appIndex = priv.indexOf(appId);
66 var appDelegate = appRepeater.itemAt(appIndex);
67 appDelegate.restore();
69 if (spread.state == "altTab") {
76 id: closeWindowShortcut
77 shortcut: Qt.AltModifier|Qt.Key_F4
78 onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
79 active: priv.focusedAppId !== ""
83 id: showSpreadShortcut
84 shortcut: Qt.MetaModifier|Qt.Key_W
85 onTriggered: spread.state = "altTab"
89 id: minimizeAllShortcut
90 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
91 onTriggered: priv.minimizeAllWindows()
95 id: maximizeWindowShortcut
96 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
97 onTriggered: priv.focusedAppDelegate.maximize()
98 active: priv.focusedAppDelegate !== null
102 id: maximizeWindowLeftShortcut
103 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
104 onTriggered: priv.focusedAppDelegate.maximizeLeft()
105 active: priv.focusedAppDelegate !== null
109 id: maximizeWindowRightShortcut
110 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
111 onTriggered: priv.focusedAppDelegate.maximizeRight()
112 active: priv.focusedAppDelegate !== null
116 id: minimizeRestoreShortcut
117 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
118 onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
119 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
120 active: priv.focusedAppDelegate !== null
126 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
127 readonly property var focusedAppDelegate: {
128 var index = indexOf(focusedAppId);
129 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
131 onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
133 property int foregroundMaximizedAppZ: -1
134 property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
136 function updateForegroundMaximizedApp() {
139 for (var i = appRepeater.count - 1; i >= 0; i--) {
140 var item = appRepeater.itemAt(i);
141 if (item && item.visuallyMaximized) {
143 tmp = Math.max(tmp, item.normalZ);
146 foregroundMaximizedAppZ = tmp;
147 foregroundMaximizedAppIndex = tmpAppId;
150 function indexOf(appId) {
151 for (var i = 0; i < ApplicationManager.count; i++) {
152 if (ApplicationManager.get(i).appId == appId) {
159 function minimizeAllWindows() {
160 for (var i = 0; i < appRepeater.count; i++) {
161 var appDelegate = appRepeater.itemAt(i);
162 if (appDelegate && !appDelegate.minimized) {
163 appDelegate.minimize();
167 ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
170 function focusNext() {
171 ApplicationManager.unfocusCurrentApplication();
172 for (var i = 0; i < appRepeater.count; i++) {
173 var appDelegate = appRepeater.itemAt(i);
174 if (appDelegate && !appDelegate.minimized) {
175 ApplicationManager.focusApplication(appDelegate.appId);
185 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
187 onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
188 onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
189 && priv.focusedAppDelegate.restoreFromMaximized();
190 onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
191 ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
197 property: "buttonsVisible"
198 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
199 && spread.state == ""
206 if (priv.focusedAppDelegate !== null && spread.state == "") {
207 if (priv.focusedAppDelegate.maximized)
208 return priv.focusedAppDelegate.title
210 return priv.focusedAppDelegate.appName
214 when: priv.focusedAppDelegate
219 property: "dropShadow"
220 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1
223 Component.onDestruction: {
224 PanelState.title = "";
225 PanelState.buttonsVisible = false;
226 PanelState.dropShadow = false;
232 objectName: "appContainer"
234 focus: spread.state !== "altTab"
239 source: root.background
240 sourceSize { height: root.height; width: root.width }
241 fillMode: Image.PreserveAspectCrop
246 model: ApplicationManager
247 objectName: "appRepeater"
249 delegate: FocusScope {
251 objectName: "appDelegate_" + appId
252 // z might be overriden in some cases by effects, but we need z ordering
253 // to calculate occlusion detection
254 property int normalZ: ApplicationManager.count - index
256 y: PanelState.panelHeight
257 focus: appId === priv.focusedAppId
258 width: decoratedWindow.width
259 height: decoratedWindow.height
260 property int requestedWidth: -1
261 property int requestedHeight: -1
262 property alias minimumWidth: decoratedWindow.minimumWidth
263 property alias minimumHeight: decoratedWindow.minimumHeight
264 property alias maximumWidth: decoratedWindow.maximumWidth
265 property alias maximumHeight: decoratedWindow.maximumHeight
266 property alias widthIncrement: decoratedWindow.widthIncrement
267 property alias heightIncrement: decoratedWindow.heightIncrement
270 id: appDelegatePrivate
271 property bool maximized: false
272 property bool maximizedLeft: false
273 property bool maximizedRight: false
274 property bool minimized: false
276 readonly property alias maximized: appDelegatePrivate.maximized
277 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
278 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
279 readonly property alias minimized: appDelegatePrivate.minimized
280 readonly property alias fullscreen: decoratedWindow.fullscreen
282 readonly property string appId: model.appId
283 property bool animationsEnabled: true
284 property alias title: decoratedWindow.title
285 readonly property string appName: model.name
286 property bool visuallyMaximized: false
287 property bool visuallyMinimized: false
289 readonly property alias appWindow: decoratedWindow.window
292 if (focus && ApplicationManager.focusedApplicationId !== appId) {
293 ApplicationManager.focusApplication(appId);
297 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
299 visible: !visuallyMinimized &&
300 !greeter.fullyShown &&
301 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
302 decoratedWindow.fullscreen ||
303 (spread.state == "altTab" && index === spread.highlightedIndex)
306 target: ApplicationManager.get(index)
307 property: "requestedState"
308 // TODO: figure out some lifecycle policy, like suspending minimized apps
309 // if running on a tablet or something.
310 // TODO: If the device has a dozen suspended apps because it was running
311 // in staged mode, when it switches to Windowed mode it will suddenly
312 // resume all those apps at once. We might want to avoid that.
313 value: ApplicationInfoInterface.RequestedRunning // Always running for now
316 function maximize(animated) {
317 animationsEnabled = (animated === undefined) || animated;
318 appDelegatePrivate.minimized = false;
319 appDelegatePrivate.maximized = true;
320 appDelegatePrivate.maximizedLeft = false;
321 appDelegatePrivate.maximizedRight = false;
323 function maximizeLeft() {
324 appDelegatePrivate.minimized = false;
325 appDelegatePrivate.maximized = false;
326 appDelegatePrivate.maximizedLeft = true;
327 appDelegatePrivate.maximizedRight = false;
329 function maximizeRight() {
330 appDelegatePrivate.minimized = false;
331 appDelegatePrivate.maximized = false;
332 appDelegatePrivate.maximizedLeft = false;
333 appDelegatePrivate.maximizedRight = true;
335 function minimize(animated) {
336 animationsEnabled = (animated === undefined) || animated;
337 appDelegatePrivate.minimized = true;
339 function restoreFromMaximized(animated) {
340 animationsEnabled = (animated === undefined) || animated;
341 appDelegatePrivate.minimized = false;
342 appDelegatePrivate.maximized = false;
343 appDelegatePrivate.maximizedLeft = false;
344 appDelegatePrivate.maximizedRight = false;
346 function restore(animated) {
347 animationsEnabled = (animated === undefined) || animated;
348 appDelegatePrivate.minimized = false;
351 else if (maximizedLeft)
353 else if (maximizedRight)
355 ApplicationManager.focusApplication(appId);
358 function playFocusAnimation() {
359 focusAnimation.start()
362 UbuntuNumberAnimation {
368 duration: UbuntuAnimation.SnapDuration
373 name: "fullscreen"; when: decoratedWindow.fullscreen
377 y: -PanelState.panelHeight
378 requestedWidth: appContainer.width;
379 requestedHeight: appContainer.height;
384 when: !appDelegate.maximized && !appDelegate.minimized
385 && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
388 visuallyMinimized: false;
389 visuallyMaximized: false
393 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
398 visuallyMinimized: false;
399 visuallyMaximized: true
402 target: decoratedWindow
403 requestedWidth: appContainer.width - root.leftMargin;
404 requestedHeight: appContainer.height;
408 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
412 y: PanelState.panelHeight
415 target: decoratedWindow
416 requestedWidth: (appContainer.width - root.leftMargin)/2
417 requestedHeight: appContainer.height - PanelState.panelHeight
421 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
424 x: (appContainer.width + root.leftMargin)/2
425 y: PanelState.panelHeight
428 target: decoratedWindow
429 requestedWidth: (appContainer.width - root.leftMargin)/2
430 requestedHeight: appContainer.height - PanelState.panelHeight
434 name: "minimized"; when: appDelegate.minimized
437 x: -appDelegate.width / 2;
438 scale: units.gu(5) / appDelegate.width;
440 visuallyMinimized: true;
441 visuallyMaximized: false
448 enabled: appDelegate.animationsEnabled
449 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
450 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
451 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
455 enabled: appDelegate.animationsEnabled
456 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
457 SequentialAnimation {
459 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
460 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
462 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
465 if (appDelegate.minimized) {
473 to: "*" //maximized and fullscreen
474 enabled: appDelegate.animationsEnabled
475 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
476 SequentialAnimation {
478 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,scale"; duration: UbuntuAnimation.FastDuration }
479 UbuntuNumberAnimation { target: decoratedWindow; properties: "requestedWidth,requestedHeight"; duration: UbuntuAnimation.FastDuration }
481 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
490 value: ApplicationManager.count + 1
491 when: index == spread.highlightedIndex && spread.ready
496 objectName: "windowResizeArea"
498 minWidth: units.gu(10)
499 minHeight: units.gu(10)
500 borderThickness: units.gu(2)
501 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
502 screenWidth: appContainer.width
503 screenHeight: appContainer.height
504 leftMargin: root.leftMargin
506 onPressed: { ApplicationManager.focusApplication(model.appId) }
508 Component.onCompleted: {
512 property bool saveStateOnDestruction: true
515 onStageAboutToBeUnloaded: {
516 resizeArea.saveWindowState();
517 resizeArea.saveStateOnDestruction = false;
518 fullscreenPolicy.active = false;
521 Component.onDestruction: {
522 if (saveStateOnDestruction) {
530 objectName: "decoratedWindow"
531 anchors.left: appDelegate.left
532 anchors.top: appDelegate.top
533 application: ApplicationManager.get(index)
534 active: ApplicationManager.focusedApplicationId === model.appId
537 requestedWidth: appDelegate.requestedWidth
538 requestedHeight: appDelegate.requestedHeight
540 onClose: ApplicationManager.stopApplication(model.appId)
541 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
542 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
543 onMinimize: appDelegate.minimize()
544 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
547 WindowedFullscreenPolicy {
550 application: decoratedWindow.application
559 // NB: it does its own positioning according to the specified edge
562 onPassed: { spread.show(); }
563 material: Component {
569 anchors.centerIn: parent
571 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
572 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
579 DirectionalDragArea {
580 direction: Direction.Leftwards
581 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
583 onDraggingChanged: { if (dragging) { spread.show(); } }
589 anchors.fill: appContainer
590 workspace: appContainer
591 focus: state == "altTab"
592 altTabPressed: root.altTabPressed
594 onPlayFocusAnimation: {
595 appRepeater.itemAt(index).playFocusAnimation();