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 // application windows never rotate independently
44 mainAppWindowOrientationAngle: shellOrientationAngle
46 orientationChangesEnabled: true
49 target: ApplicationManager
51 if (spread.state == "altTab") {
55 ApplicationManager.focusApplication(appId);
58 onApplicationRemoved: {
63 var appIndex = priv.indexOf(appId);
64 var appDelegate = appRepeater.itemAt(appIndex);
65 appDelegate.restore();
67 if (spread.state == "altTab") {
74 id: closeWindowShortcut
75 shortcut: Qt.AltModifier|Qt.Key_F4
76 onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
77 active: priv.focusedAppId !== ""
81 id: showSpreadShortcut
82 shortcut: Qt.MetaModifier|Qt.Key_W
83 onTriggered: spread.state = "altTab"
87 id: minimizeAllShortcut
88 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
89 onTriggered: priv.minimizeAllWindows()
93 id: maximizeWindowShortcut
94 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
95 onTriggered: priv.focusedAppDelegate.maximize()
96 active: priv.focusedAppDelegate !== null
100 id: maximizeWindowLeftShortcut
101 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
102 onTriggered: priv.focusedAppDelegate.maximizeLeft()
103 active: priv.focusedAppDelegate !== null
107 id: maximizeWindowRightShortcut
108 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
109 onTriggered: priv.focusedAppDelegate.maximizeRight()
110 active: priv.focusedAppDelegate !== null
114 id: minimizeRestoreShortcut
115 shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
116 onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
117 ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
118 active: priv.focusedAppDelegate !== null
124 readonly property string focusedAppId: ApplicationManager.focusedApplicationId
125 readonly property var focusedAppDelegate: {
126 var index = indexOf(focusedAppId);
127 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
129 onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
131 property int foregroundMaximizedAppZ: -1
132 property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
134 function updateForegroundMaximizedApp() {
137 for (var i = appRepeater.count - 1; i >= 0; i--) {
138 var item = appRepeater.itemAt(i);
139 if (item && item.visuallyMaximized) {
141 tmp = Math.max(tmp, item.normalZ);
144 foregroundMaximizedAppZ = tmp;
145 foregroundMaximizedAppIndex = tmpAppId;
148 function indexOf(appId) {
149 for (var i = 0; i < ApplicationManager.count; i++) {
150 if (ApplicationManager.get(i).appId == appId) {
157 function minimizeAllWindows() {
158 for (var i = 0; i < appRepeater.count; i++) {
159 var appDelegate = appRepeater.itemAt(i);
160 if (appDelegate && !appDelegate.minimized) {
161 appDelegate.minimize();
165 ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
168 function focusNext() {
169 ApplicationManager.unfocusCurrentApplication();
170 for (var i = 0; i < appRepeater.count; i++) {
171 var appDelegate = appRepeater.itemAt(i);
172 if (appDelegate && !appDelegate.minimized) {
173 ApplicationManager.focusApplication(appDelegate.appId);
183 ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
185 onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
186 onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
187 && priv.focusedAppDelegate.restoreFromMaximized();
188 onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
189 ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
195 property: "buttonsVisible"
196 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
197 && spread.state == ""
204 if (priv.focusedAppDelegate !== null && spread.state == "") {
205 if (priv.focusedAppDelegate.maximized)
206 return priv.focusedAppDelegate.title
208 return priv.focusedAppDelegate.appName
212 when: priv.focusedAppDelegate
217 property: "dropShadow"
218 value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1
221 Component.onDestruction: {
222 PanelState.title = "";
223 PanelState.buttonsVisible = false;
224 PanelState.dropShadow = false;
230 objectName: "appContainer"
232 focus: spread.state !== "altTab"
237 source: root.background
238 sourceSize { height: root.height; width: root.width }
239 fillMode: Image.PreserveAspectCrop
244 model: ApplicationManager
245 objectName: "appRepeater"
247 delegate: FocusScope {
249 objectName: "appDelegate_" + appId
250 // z might be overriden in some cases by effects, but we need z ordering
251 // to calculate occlusion detection
252 property int normalZ: ApplicationManager.count - index
254 y: PanelState.panelHeight
255 focus: appId === priv.focusedAppId
256 width: decoratedWindow.width
257 height: decoratedWindow.height
258 property alias requestedWidth: decoratedWindow.requestedWidth
259 property alias requestedHeight: decoratedWindow.requestedHeight
260 property alias minimumWidth: decoratedWindow.minimumWidth
261 property alias minimumHeight: decoratedWindow.minimumHeight
262 property alias maximumWidth: decoratedWindow.maximumWidth
263 property alias maximumHeight: decoratedWindow.maximumHeight
264 property alias widthIncrement: decoratedWindow.widthIncrement
265 property alias heightIncrement: decoratedWindow.heightIncrement
268 id: appDelegatePrivate
269 property bool maximized: false
270 property bool maximizedLeft: false
271 property bool maximizedRight: false
272 property bool minimized: false
274 readonly property alias maximized: appDelegatePrivate.maximized
275 readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
276 readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
277 readonly property alias minimized: appDelegatePrivate.minimized
278 readonly property alias fullscreen: decoratedWindow.fullscreen
280 readonly property string appId: model.appId
281 property bool animationsEnabled: true
282 property alias title: decoratedWindow.title
283 readonly property string appName: model.name
284 property bool visuallyMaximized: false
285 property bool visuallyMinimized: false
288 if (focus && ApplicationManager.focusedApplicationId !== appId) {
289 ApplicationManager.focusApplication(appId);
293 onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
295 visible: !visuallyMinimized &&
296 !greeter.fullyShown &&
297 (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
298 decoratedWindow.fullscreen ||
299 (spread.state == "altTab" && index === spread.highlightedIndex)
302 target: ApplicationManager.get(index)
303 property: "requestedState"
304 // TODO: figure out some lifecycle policy, like suspending minimized apps
305 // if running on a tablet or something.
306 // TODO: If the device has a dozen suspended apps because it was running
307 // in staged mode, when it switches to Windowed mode it will suddenly
308 // resume all those apps at once. We might want to avoid that.
309 value: ApplicationInfoInterface.RequestedRunning // Always running for now
312 function maximize(animated) {
313 animationsEnabled = (animated === undefined) || animated;
314 appDelegatePrivate.minimized = false;
315 appDelegatePrivate.maximized = true;
316 appDelegatePrivate.maximizedLeft = false;
317 appDelegatePrivate.maximizedRight = false;
319 function maximizeLeft() {
320 appDelegatePrivate.minimized = false;
321 appDelegatePrivate.maximized = false;
322 appDelegatePrivate.maximizedLeft = true;
323 appDelegatePrivate.maximizedRight = false;
325 function maximizeRight() {
326 appDelegatePrivate.minimized = false;
327 appDelegatePrivate.maximized = false;
328 appDelegatePrivate.maximizedLeft = false;
329 appDelegatePrivate.maximizedRight = true;
331 function minimize(animated) {
332 animationsEnabled = (animated === undefined) || animated;
333 appDelegatePrivate.minimized = true;
335 function restoreFromMaximized(animated) {
336 animationsEnabled = (animated === undefined) || animated;
337 appDelegatePrivate.minimized = false;
338 appDelegatePrivate.maximized = false;
339 appDelegatePrivate.maximizedLeft = false;
340 appDelegatePrivate.maximizedRight = false;
342 function restore(animated) {
343 animationsEnabled = (animated === undefined) || animated;
344 appDelegatePrivate.minimized = false;
347 else if (maximizedLeft)
349 else if (maximizedRight)
351 ApplicationManager.focusApplication(appId);
354 function playFocusAnimation() {
355 focusAnimation.start()
358 UbuntuNumberAnimation {
364 duration: UbuntuAnimation.SnapDuration
369 name: "fullscreen"; when: decoratedWindow.fullscreen
372 x: 0; y: -PanelState.panelHeight
373 requestedWidth: appContainer.width; requestedHeight: appContainer.height;
378 when: !appDelegate.maximized && !appDelegate.minimized
379 && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
382 visuallyMinimized: false;
383 visuallyMaximized: false
387 name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
390 x: root.leftMargin; y: 0;
391 requestedWidth: appContainer.width - root.leftMargin; requestedHeight: appContainer.height;
392 visuallyMinimized: false;
393 visuallyMaximized: true
397 name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
398 PropertyChanges { target: appDelegate; x: root.leftMargin; y: PanelState.panelHeight;
399 requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
402 name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
403 PropertyChanges { target: appDelegate; x: (appContainer.width + root.leftMargin)/2; y: PanelState.panelHeight;
404 requestedWidth: (appContainer.width - root.leftMargin)/2; requestedHeight: appContainer.height - PanelState.panelHeight }
407 name: "minimized"; when: appDelegate.minimized
410 x: -appDelegate.width / 2;
411 scale: units.gu(5) / appDelegate.width;
413 visuallyMinimized: true;
414 visuallyMaximized: false
421 enabled: appDelegate.animationsEnabled
422 PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
423 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
427 enabled: appDelegate.animationsEnabled
428 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
429 SequentialAnimation {
430 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
431 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
434 if (appDelegate.minimized) {
442 to: "*" //maximized and fullscreen
443 enabled: appDelegate.animationsEnabled
444 PropertyAction { target: appDelegate; property: "visuallyMinimized" }
445 SequentialAnimation {
446 UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
447 PropertyAction { target: appDelegate; property: "visuallyMaximized" }
456 value: ApplicationManager.count + 1
457 when: index == spread.highlightedIndex && spread.ready
461 objectName: "windowResizeArea"
463 minWidth: units.gu(10)
464 minHeight: units.gu(10)
465 borderThickness: units.gu(2)
466 windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
467 screenWidth: appContainer.width
468 screenHeight: appContainer.height
469 leftMargin: root.leftMargin
471 onPressed: { ApplicationManager.focusApplication(model.appId) }
476 objectName: "decoratedWindow"
477 anchors.left: appDelegate.left
478 anchors.top: appDelegate.top
479 application: ApplicationManager.get(index)
480 active: ApplicationManager.focusedApplicationId === model.appId
483 onClose: ApplicationManager.stopApplication(model.appId)
484 onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
485 ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
486 onMinimize: appDelegate.minimize()
487 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
496 // NB: it does its own positioning according to the specified edge
499 onPassed: { spread.show(); }
500 material: Component {
506 anchors.centerIn: parent
508 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.5)}
509 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
516 DirectionalDragArea {
517 direction: Direction.Leftwards
518 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
520 onDraggingChanged: { if (dragging) { spread.show(); } }
526 anchors.fill: appContainer
527 workspace: appContainer
528 focus: state == "altTab"
529 altTabPressed: root.altTabPressed
531 onPlayFocusAnimation: {
532 appRepeater.itemAt(index).playFocusAnimation();