Unity 8
DesktopStage.qml
1 /*
2  * Copyright (C) 2014-2015 Canonical, Ltd.
3  *
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.
7  *
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.
12  *
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/>.
15  */
16 
17 import QtQuick 2.4
18 import Ubuntu.Components 1.3
19 import Unity.Application 0.1
20 import "../Components/PanelState"
21 import "../Components"
22 import Utils 0.1
23 import Ubuntu.Gestures 0.1
24 import GlobalShortcut 1.0
25 
26 AbstractStage {
27  id: root
28  anchors.fill: parent
29 
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);
36  }
37  }
38 
39  mainApp: ApplicationManager.focusedApplicationId
40  ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
41  : null
42 
43  Connections {
44  target: ApplicationManager
45  onApplicationAdded: {
46  if (spread.state == "altTab") {
47  spread.state = "";
48  }
49 
50  ApplicationManager.focusApplication(appId);
51  }
52 
53  onApplicationRemoved: {
54  priv.focusNext();
55  }
56 
57  onFocusRequested: {
58  var appIndex = priv.indexOf(appId);
59  var appDelegate = appRepeater.itemAt(appIndex);
60  appDelegate.restore();
61 
62  if (spread.state == "altTab") {
63  spread.cancel();
64  }
65  }
66  }
67 
68  GlobalShortcut {
69  id: closeWindowShortcut
70  shortcut: Qt.AltModifier|Qt.Key_F4
71  onTriggered: ApplicationManager.stopApplication(priv.focusedAppId)
72  active: priv.focusedAppId !== ""
73  }
74 
75  GlobalShortcut {
76  id: showSpreadShortcut
77  shortcut: Qt.MetaModifier|Qt.Key_W
78  onTriggered: spread.state = "altTab"
79  }
80 
81  GlobalShortcut {
82  id: minimizeAllShortcut
83  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_D
84  onTriggered: priv.minimizeAllWindows()
85  }
86 
87  GlobalShortcut {
88  id: maximizeWindowShortcut
89  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Up
90  onTriggered: priv.focusedAppDelegate.maximize()
91  active: priv.focusedAppDelegate !== null
92  }
93 
94  GlobalShortcut {
95  id: maximizeWindowLeftShortcut
96  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Left
97  onTriggered: priv.focusedAppDelegate.maximizeLeft()
98  active: priv.focusedAppDelegate !== null
99  }
100 
101  GlobalShortcut {
102  id: maximizeWindowRightShortcut
103  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Right
104  onTriggered: priv.focusedAppDelegate.maximizeRight()
105  active: priv.focusedAppDelegate !== null
106  }
107 
108  GlobalShortcut {
109  id: minimizeRestoreShortcut
110  shortcut: Qt.MetaModifier|Qt.ControlModifier|Qt.Key_Down
111  onTriggered: priv.focusedAppDelegate.maximized || priv.focusedAppDelegate.maximizedLeft || priv.focusedAppDelegate.maximizedRight
112  ? priv.focusedAppDelegate.restoreFromMaximized() : priv.focusedAppDelegate.minimize()
113  active: priv.focusedAppDelegate !== null
114  }
115 
116  QtObject {
117  id: priv
118 
119  readonly property string focusedAppId: ApplicationManager.focusedApplicationId
120  readonly property var focusedAppDelegate: {
121  var index = indexOf(focusedAppId);
122  return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
123  }
124  onFocusedAppDelegateChanged: updateForegroundMaximizedApp();
125 
126  property int foregroundMaximizedAppZ: -1
127  property int foregroundMaximizedAppIndex: -1 // for stuff like drop shadow and focusing maximized app by clicking panel
128 
129  function updateForegroundMaximizedApp() {
130  var tmp = -1;
131  var tmpAppId = -1;
132  for (var i = appRepeater.count - 1; i >= 0; i--) {
133  var item = appRepeater.itemAt(i);
134  if (item && item.visuallyMaximized) {
135  tmpAppId = i;
136  tmp = Math.max(tmp, item.normalZ);
137  }
138  }
139  foregroundMaximizedAppZ = tmp;
140  foregroundMaximizedAppIndex = tmpAppId;
141  }
142 
143  function indexOf(appId) {
144  for (var i = 0; i < ApplicationManager.count; i++) {
145  if (ApplicationManager.get(i).appId == appId) {
146  return i;
147  }
148  }
149  return -1;
150  }
151 
152  function minimizeAllWindows() {
153  for (var i = 0; i < appRepeater.count; i++) {
154  var appDelegate = appRepeater.itemAt(i);
155  if (appDelegate && !appDelegate.minimized) {
156  appDelegate.minimize();
157  }
158  }
159 
160  ApplicationManager.unfocusCurrentApplication(); // no app should have focus at this point
161  }
162 
163  function focusNext() {
164  ApplicationManager.unfocusCurrentApplication();
165  for (var i = 0; i < appRepeater.count; i++) {
166  var appDelegate = appRepeater.itemAt(i);
167  if (appDelegate && !appDelegate.minimized) {
168  ApplicationManager.focusApplication(appDelegate.appId);
169  return;
170  }
171  }
172  }
173  }
174 
175  Connections {
176  target: PanelState
177  onClose: {
178  ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
179  }
180  onMinimize: priv.focusedAppDelegate && priv.focusedAppDelegate.minimize();
181  onMaximize: priv.focusedAppDelegate // don't restore minimized apps when double clicking the panel
182  && priv.focusedAppDelegate.restoreFromMaximized();
183  onFocusMaximizedApp: if (priv.foregroundMaximizedAppIndex != -1) {
184  ApplicationManager.focusApplication(appRepeater.itemAt(priv.foregroundMaximizedAppIndex).appId);
185  }
186  }
187 
188  Binding {
189  target: PanelState
190  property: "buttonsVisible"
191  value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
192  && spread.state == ""
193  }
194 
195  Binding {
196  target: PanelState
197  property: "title"
198  value: {
199  if (priv.focusedAppDelegate !== null && spread.state == "") {
200  if (priv.focusedAppDelegate.maximized)
201  return priv.focusedAppDelegate.title
202  else
203  return priv.focusedAppDelegate.appName
204  }
205  return ""
206  }
207  when: priv.focusedAppDelegate
208  }
209 
210  Binding {
211  target: PanelState
212  property: "dropShadow"
213  value: priv.focusedAppDelegate && !priv.focusedAppDelegate.maximized && priv.foregroundMaximizedAppIndex !== -1
214  }
215 
216  Component.onDestruction: {
217  PanelState.title = "";
218  PanelState.buttonsVisible = false;
219  PanelState.dropShadow = false;
220  }
221 
222  FocusScope {
223  id: appContainer
224  objectName: "appContainer"
225  anchors.fill: parent
226  focus: spread.state !== "altTab"
227 
228  CrossFadeImage {
229  id: wallpaper
230  anchors.fill: parent
231  source: root.background
232  sourceSize { height: root.height; width: root.width }
233  fillMode: Image.PreserveAspectCrop
234  }
235 
236  Repeater {
237  id: appRepeater
238  model: ApplicationManager
239  objectName: "appRepeater"
240 
241  delegate: FocusScope {
242  id: appDelegate
243  objectName: "appDelegate_" + appId
244  // z might be overriden in some cases by effects, but we need z ordering
245  // to calculate occlusion detection
246  property int normalZ: ApplicationManager.count - index
247  z: normalZ
248  y: PanelState.panelHeight
249  focus: appId === priv.focusedAppId
250  width: decoratedWindow.width
251  height: decoratedWindow.height
252  property alias requestedWidth: decoratedWindow.requestedWidth
253  property alias requestedHeight: decoratedWindow.requestedHeight
254 
255  QtObject {
256  id: appDelegatePrivate
257  property bool maximized: false
258  property bool maximizedLeft: false
259  property bool maximizedRight: false
260  property bool minimized: false
261  }
262  readonly property alias maximized: appDelegatePrivate.maximized
263  readonly property alias maximizedLeft: appDelegatePrivate.maximizedLeft
264  readonly property alias maximizedRight: appDelegatePrivate.maximizedRight
265  readonly property alias minimized: appDelegatePrivate.minimized
266 
267  readonly property string appId: model.appId
268  property bool animationsEnabled: true
269  property alias title: decoratedWindow.title
270  readonly property string appName: model.name
271  property bool visuallyMaximized: false
272  property bool visuallyMinimized: false
273 
274  onFocusChanged: {
275  if (focus && ApplicationManager.focusedApplicationId !== appId) {
276  ApplicationManager.focusApplication(appId);
277  }
278  }
279 
280  onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
281 
282  visible: !visuallyMinimized &&
283  !greeter.fullyShown &&
284  (priv.foregroundMaximizedAppZ === -1 || priv.foregroundMaximizedAppZ <= z) ||
285  (spread.state == "altTab" && index === spread.highlightedIndex)
286 
287  Binding {
288  target: ApplicationManager.get(index)
289  property: "requestedState"
290  // TODO: figure out some lifecycle policy, like suspending minimized apps
291  // if running on a tablet or something.
292  // TODO: If the device has a dozen suspended apps because it was running
293  // in staged mode, when it switches to Windowed mode it will suddenly
294  // resume all those apps at once. We might want to avoid that.
295  value: ApplicationInfoInterface.RequestedRunning // Always running for now
296  }
297 
298  function maximize(animated) {
299  animationsEnabled = (animated === undefined) || animated;
300  appDelegatePrivate.minimized = false;
301  appDelegatePrivate.maximized = true;
302  appDelegatePrivate.maximizedLeft = false;
303  appDelegatePrivate.maximizedRight = false;
304  }
305  function maximizeLeft() {
306  appDelegatePrivate.minimized = false;
307  appDelegatePrivate.maximized = false;
308  appDelegatePrivate.maximizedLeft = true;
309  appDelegatePrivate.maximizedRight = false;
310  }
311  function maximizeRight() {
312  appDelegatePrivate.minimized = false;
313  appDelegatePrivate.maximized = false;
314  appDelegatePrivate.maximizedLeft = false;
315  appDelegatePrivate.maximizedRight = true;
316  }
317  function minimize(animated) {
318  animationsEnabled = (animated === undefined) || animated;
319  appDelegatePrivate.minimized = true;
320  }
321  function restoreFromMaximized(animated) {
322  animationsEnabled = (animated === undefined) || animated;
323  appDelegatePrivate.minimized = false;
324  appDelegatePrivate.maximized = false;
325  appDelegatePrivate.maximizedLeft = false;
326  appDelegatePrivate.maximizedRight = false;
327  }
328  function restore(animated) {
329  animationsEnabled = (animated === undefined) || animated;
330  appDelegatePrivate.minimized = false;
331  if (maximized)
332  maximize();
333  else if (maximizedLeft)
334  maximizeLeft();
335  else if (maximizedRight)
336  maximizeRight();
337  ApplicationManager.focusApplication(appId);
338  }
339 
340  states: [
341  State {
342  name: "fullscreen"; when: decoratedWindow.fullscreen
343  extend: "maximized"
344  PropertyChanges {
345  target: appDelegate;
346  y: -PanelState.panelHeight
347  }
348  },
349  State {
350  name: "normal";
351  when: !appDelegate.maximized && !appDelegate.minimized
352  && !appDelegate.maximizedLeft && !appDelegate.maximizedRight
353  PropertyChanges {
354  target: appDelegate;
355  visuallyMinimized: false;
356  visuallyMaximized: false
357  }
358  },
359  State {
360  name: "maximized"; when: appDelegate.maximized && !appDelegate.minimized
361  PropertyChanges {
362  target: appDelegate;
363  x: 0; y: 0;
364  requestedWidth: root.width; requestedHeight: root.height;
365  visuallyMinimized: false;
366  visuallyMaximized: true
367  }
368  },
369  State {
370  name: "maximizedLeft"; when: appDelegate.maximizedLeft && !appDelegate.minimized
371  PropertyChanges { target: appDelegate; x: 0; y: PanelState.panelHeight;
372  requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
373  },
374  State {
375  name: "maximizedRight"; when: appDelegate.maximizedRight && !appDelegate.minimized
376  PropertyChanges { target: appDelegate; x: root.width/2; y: PanelState.panelHeight;
377  requestedWidth: root.width/2; requestedHeight: root.height - PanelState.panelHeight }
378  },
379  State {
380  name: "minimized"; when: appDelegate.minimized
381  PropertyChanges {
382  target: appDelegate;
383  x: -appDelegate.width / 2;
384  scale: units.gu(5) / appDelegate.width;
385  opacity: 0
386  visuallyMinimized: true;
387  visuallyMaximized: false
388  }
389  }
390  ]
391  transitions: [
392  Transition {
393  to: "normal"
394  enabled: appDelegate.animationsEnabled
395  PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
396  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
397  },
398  Transition {
399  to: "minimized"
400  enabled: appDelegate.animationsEnabled
401  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
402  SequentialAnimation {
403  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
404  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
405  ScriptAction {
406  script: {
407  if (appDelegate.minimized) {
408  priv.focusNext();
409  }
410  }
411  }
412  }
413  },
414  Transition {
415  to: "*" //maximized and fullscreen
416  enabled: appDelegate.animationsEnabled
417  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
418  SequentialAnimation {
419  UbuntuNumberAnimation { target: appDelegate; properties: "x,y,opacity,requestedWidth,requestedHeight,scale"; duration: UbuntuAnimation.FastDuration }
420  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
421  }
422  }
423  ]
424 
425  Binding {
426  id: previewBinding
427  target: appDelegate
428  property: "z"
429  value: ApplicationManager.count + 1
430  when: index == spread.highlightedIndex && blurLayer.ready
431  }
432 
433  WindowResizeArea {
434  objectName: "windowResizeArea"
435  target: appDelegate
436  minWidth: units.gu(10)
437  minHeight: units.gu(10)
438  borderThickness: units.gu(2)
439  windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
440  screenWidth: root.width
441  screenHeight: root.height
442 
443  onPressed: { ApplicationManager.focusApplication(model.appId) }
444  }
445 
446  DecoratedWindow {
447  id: decoratedWindow
448  objectName: "decoratedWindow"
449  anchors.left: appDelegate.left
450  anchors.top: appDelegate.top
451  application: ApplicationManager.get(index)
452  active: ApplicationManager.focusedApplicationId === model.appId
453  focus: true
454 
455  onClose: ApplicationManager.stopApplication(model.appId)
456  onMaximize: appDelegate.maximized || appDelegate.maximizedLeft || appDelegate.maximizedRight
457  ? appDelegate.restoreFromMaximized() : appDelegate.maximize()
458  onMinimize: appDelegate.minimize()
459  onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
460  }
461  }
462  }
463  }
464 
465  BlurLayer {
466  id: blurLayer
467  anchors.fill: parent
468  source: appContainer
469  visible: false
470  }
471 
472  Rectangle {
473  id: spreadBackground
474  anchors.fill: parent
475  color: "#55000000"
476  visible: false
477  }
478 
479  MouseArea {
480  id: eventEater
481  anchors.fill: parent
482  visible: spreadBackground.visible
483  enabled: visible
484  }
485 
486  EdgeBarrier {
487  id: edgeBarrier
488 
489  // NB: it does its own positioning according to the specified edge
490  edge: Qt.RightEdge
491 
492  onPassed: { spread.show(); }
493  material: Component {
494  Item {
495  Rectangle {
496  width: parent.height
497  height: parent.width
498  rotation: 90
499  anchors.centerIn: parent
500  gradient: Gradient {
501  GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
502  GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}
503  }
504  }
505  }
506  }
507  }
508 
509  DirectionalDragArea {
510  direction: Direction.Leftwards
511  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
512  width: units.gu(1)
513  onDraggingChanged: { if (dragging) { spread.show(); } }
514  }
515 
516  DesktopSpread {
517  id: spread
518  objectName: "spread"
519  anchors.fill: parent
520  workspace: appContainer
521  focus: state == "altTab"
522  altTabPressed: root.altTabPressed
523  }
524 }