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  * Authors: Michael Zanetti <michael.zanetti@canonical.com>
17  */
18 
19 import QtQuick 2.4
20 import QtQuick.Layouts 1.1
21 import Ubuntu.Components 1.3
22 import Unity.Application 0.1
23 import "../Components"
24 import "../Components/PanelState"
25 import "../Components"
26 import Utils 0.1
27 import Ubuntu.Gestures 0.1
28 
29 AbstractStage {
30  id: root
31  anchors.fill: parent
32 
33  // functions to be called from outside
34  function updateFocusedAppOrientation() { /* TODO */ }
35  function updateFocusedAppOrientationAnimated() { /* TODO */}
36 
37  mainApp: ApplicationManager.focusedApplicationId
38  ? ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
39  : null
40 
41  Connections {
42  target: ApplicationManager
43  onApplicationAdded: {
44  if (spread.state == "altTab") {
45  spread.state = "";
46  }
47 
48  ApplicationManager.requestFocusApplication(appId)
49  }
50 
51  onFocusRequested: {
52  var appIndex = priv.indexOf(appId);
53  var appDelegate = appRepeater.itemAt(appIndex);
54  appDelegate.minimized = false;
55  ApplicationManager.focusApplication(appId)
56 
57  if (spread.state == "altTab") {
58  spread.cancel()
59  }
60  }
61  }
62 
63  QtObject {
64  id: priv
65 
66  readonly property string focusedAppId: ApplicationManager.focusedApplicationId
67  readonly property var focusedAppDelegate: {
68  var index = indexOf(focusedAppId);
69  return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
70  }
71  property int foregroundMaximizedAppIdIndex: -1
72 
73  function updateForegroundMaximizedApp() {
74  for (var i = 0; i < appRepeater.count; i++) {
75  var item = appRepeater.itemAt(i);
76 
77  if (item && item.visuallyMaximized) {
78  var app = ApplicationManager.get(i);
79  if (app) {
80  foregroundMaximizedAppIdIndex = i;
81  return;
82  }
83  }
84  }
85  foregroundMaximizedAppIdIndex = -1;
86  }
87 
88  function indexOf(appId) {
89  for (var i = 0; i < ApplicationManager.count; i++) {
90  if (ApplicationManager.get(i).appId == appId) {
91  return i;
92  }
93  }
94  return -1;
95  }
96  }
97 
98  Connections {
99  target: PanelState
100  onClose: {
101  ApplicationManager.stopApplication(ApplicationManager.focusedApplicationId)
102  }
103  onMinimize: appRepeater.itemAt(0).minimize();
104  onMaximize: appRepeater.itemAt(0).unmaximize();
105  }
106 
107  Binding {
108  target: PanelState
109  property: "buttonsVisible"
110  value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.state === "maximized"
111  }
112  Component.onDestruction: PanelState.buttonsVisible = false;
113 
114  FocusScope {
115  id: appContainer
116  objectName: "appContainer"
117  anchors.fill: parent
118  focus: spread.state !== "altTab"
119 
120  CrossFadeImage {
121  id: wallpaper
122  anchors.fill: parent
123  source: root.background
124  sourceSize { height: root.height; width: root.width }
125  fillMode: Image.PreserveAspectCrop
126  }
127 
128  Repeater {
129  id: appRepeater
130  model: ApplicationManager
131  objectName: "appRepeater"
132 
133  onItemAdded: priv.updateForegroundMaximizedApp()
134  onItemRemoved: priv.updateForegroundMaximizedApp()
135 
136  delegate: FocusScope {
137  id: appDelegate
138  objectName: "stageDelegate_" + model.appId
139  z: ApplicationManager.count - index
140  y: units.gu(3)
141  width: units.gu(60)
142  height: units.gu(50)
143  focus: model.appId === priv.focusedAppId
144 
145  property bool maximized: false
146  property bool minimized: false
147  property bool animationsEnabled: true
148 
149  property bool visuallyMaximized: false
150  property bool visuallyMinimized: false
151 
152  onFocusChanged: {
153  if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
154  ApplicationManager.focusApplication(model.appId);
155  }
156  }
157 
158  onZChanged: priv.updateForegroundMaximizedApp()
159  onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
160 
161  visible: !visuallyMinimized &&
162  !greeter.fullyShown &&
163  (priv.foregroundMaximizedAppIdIndex === -1 || priv.foregroundMaximizedAppIdIndex >= index) ||
164  (spread.state == "altTab" && index === spread.highlightedIndex)
165 
166  onVisibleChanged: console.log("VISIBLE", model.appId, visible)
167 
168  Binding {
169  target: ApplicationManager.get(index)
170  property: "requestedState"
171  // TODO: figure out some lifecycle policy, like suspending minimized apps
172  // if running on a tablet or something.
173  // TODO: If the device has a dozen suspended apps because it was running
174  // in staged mode, when it switches to Windowed mode it will suddenly
175  // resume all those apps at once. We might want to avoid that.
176  value: ApplicationInfoInterface.RequestedRunning // Always running for now
177  }
178 
179  function maximize(animated) {
180  animationsEnabled = (animated === undefined) || animated;
181  minimized = false;
182  maximized = true;
183  }
184  function minimize(animated) {
185  animationsEnabled = (animated === undefined) || animated;
186  maximized = false;
187  minimized = true;
188  }
189  function unmaximize(animated) {
190  animationsEnabled = (animated === undefined) || animated;
191  minimized = false;
192  maximized = false;
193  }
194 
195  states: [
196  State {
197  name: "normal";
198  when: !appDelegate.maximized && !appDelegate.minimized
199  PropertyChanges {
200  target: appDelegate;
201  visuallyMinimized: false;
202  visuallyMaximized: false
203  }
204  },
205  State {
206  name: "maximized"; when: appDelegate.maximized
207  PropertyChanges {
208  target: appDelegate;
209  x: 0; y: 0;
210  width: root.width; height: root.height;
211  visuallyMinimized: false;
212  visuallyMaximized: true
213  }
214  },
215  State {
216  name: "minimized"; when: appDelegate.minimized
217  PropertyChanges {
218  target: appDelegate;
219  x: -appDelegate.width / 2;
220  scale: units.gu(5) / appDelegate.width;
221  opacity: 0
222  visuallyMinimized: true;
223  visuallyMaximized: false
224  }
225  }
226  ]
227  transitions: [
228  Transition {
229  to: "normal"
230  enabled: appDelegate.animationsEnabled
231  PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
232  PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
233  },
234  Transition {
235  to: "maximized"
236  enabled: appDelegate.animationsEnabled
237  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
238  SequentialAnimation {
239  PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
240  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
241  }
242  },
243  Transition {
244  to: "minimized"
245  enabled: appDelegate.animationsEnabled
246  PropertyAction { target: appDelegate; property: "visuallyMaximized" }
247  SequentialAnimation {
248  PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
249  PropertyAction { target: appDelegate; property: "visuallyMinimized" }
250  }
251  },
252  Transition {
253  from: ""
254  to: "altTab"
255  PropertyAction { target: appDelegate; properties: "y,angle,z,itemScale,itemScaleOriginY" }
256  PropertyAction { target: decoratedWindow; properties: "anchors.topMargin" }
257  PropertyAnimation {
258  target: appDelegate; properties: "x"
259  from: root.width
260  duration: rightEdgePushArea.containsMouse ? UbuntuAnimation.FastDuration :0
261  easing: UbuntuAnimation.StandardEasing
262  }
263  }
264  ]
265 
266  Binding {
267  id: previewBinding
268  target: appDelegate
269  property: "z"
270  value: ApplicationManager.count + 1
271  when: index == spread.highlightedIndex && blurLayer.ready
272  }
273 
274  WindowResizeArea {
275  objectName: "windowResizeArea"
276  target: appDelegate
277  minWidth: units.gu(10)
278  minHeight: units.gu(10)
279  borderThickness: units.gu(2)
280  windowId: model.appId // FIXME: Change this to point to windowId once we have such a thing
281 
282  onPressed: { ApplicationManager.focusApplication(model.appId) }
283  }
284 
285  DecoratedWindow {
286  id: decoratedWindow
287  objectName: "decoratedWindow"
288  anchors.left: appDelegate.left
289  anchors.top: appDelegate.top
290  width: appDelegate.width
291  height: appDelegate.height
292  application: ApplicationManager.get(index)
293  active: ApplicationManager.focusedApplicationId === model.appId
294  focus: true
295 
296  onClose: ApplicationManager.stopApplication(model.appId)
297  onMaximize: appDelegate.maximized ? appDelegate.unmaximize() : appDelegate.maximize()
298  onMinimize: appDelegate.minimize()
299  onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
300  }
301  }
302  }
303  }
304 
305  BlurLayer {
306  id: blurLayer
307  anchors.fill: parent
308  source: appContainer
309  visible: false
310  }
311 
312  Rectangle {
313  id: spreadBackground
314  anchors.fill: parent
315  color: "#55000000"
316  visible: false
317  }
318 
319  MouseArea {
320  id: eventEater
321  anchors.fill: parent
322  visible: spreadBackground.visible
323  enabled: visible
324  }
325 
326  DesktopSpread {
327  id: spread
328  objectName: "spread"
329  anchors.fill: parent
330  workspace: appContainer
331  focus: state == "altTab"
332  altTabPressed: root.altTabPressed
333  }
334 }