Unity 8
 All Classes Functions Properties
StageWithSideStage.qml
1 /*
2  * Copyright (C) 2014 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.0
18 import Ubuntu.Components 0.1
19 import "../Components"
20 import Unity.Application 0.1
21 import Ubuntu.Gestures 0.1
22 
23 Item {
24  id: root
25  objectName: "stages"
26  anchors.fill: parent
27 
28  // Controls to be set from outside
29  property bool shown: false
30  property bool moving: false
31  property int dragAreaWidth
32 
33  // State information propagated to the outside
34  readonly property bool painting: mainStageImage.visible || sideStageImage.visible || sideStageSnapAnimation.running
35  property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
36  property bool overlayMode: (sideStageImage.shown && priv.mainStageAppId.length == 0) || priv.overlayOverride
37  || (priv.mainStageAppId.length == 0 && sideStageSnapAnimation.running)
38 
39  readonly property int overlayWidth: priv.overlayOverride ? 0 : priv.sideStageWidth
40 
41  onShownChanged: {
42  if (!shown) {
43  priv.mainStageAppId = "";
44  }
45  }
46 
47  onMovingChanged: {
48  if (moving) {
49  if (!priv.mainStageAppId && !priv.sideStageAppId) {
50  // Pulling in from the right, make the last used (topmost) app visible
51  var application = ApplicationManager.get(0);
52  if (application.stage == ApplicationInfoInterface.SideStage) {
53  sideStageImage.application = application;
54  sideStageImage.x = root.width - sideStageImage.width
55  sideStageImage.visible = true;
56  } else {
57  mainStageImage.application = application;
58  mainStageImage.visible = true;
59  }
60  } else {
61  priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
62  if (priv.focusedApplicationId == priv.sideStageAppId) {
63  priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
64  }
65  }
66  } else {
67  mainStageImage.visible = false;
68  sideStageImage.visible = false;
69  }
70  }
71 
72  QtObject {
73  id: priv
74 
75  property int sideStageWidth: units.gu(40)
76 
77 
78  property string sideStageAppId
79  property string mainStageAppId
80 
81 
82  property var sideStageApp: ApplicationManager.findApplication(sideStageAppId)
83  property var mainStageApp: ApplicationManager.findApplication(mainStageAppId)
84 
85  property string sideStageScreenshot: sideStageApp ? sideStageApp.screenshot : ""
86  property string mainStageScreenshot: mainStageApp ? mainStageApp.screenshot : ""
87 
88  property string focusedApplicationId: ApplicationManager.focusedApplicationId
89  property var focusedApplication: ApplicationManager.findApplication(focusedApplicationId)
90  property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
91 
92  property bool waitingForMainScreenshot: false
93  property bool waitingForSideScreenshot: false
94  property bool waitingForScreenshots: waitingForMainScreenshot || waitingForSideScreenshot
95 
96  property string startingAppId: ""
97 
98  // Keep overlayMode even if there is no focused app (to allow pulling in the sidestage from the right)
99  property bool overlayOverride: false
100 
101  onFocusedApplicationChanged: {
102  if (focusedApplication) {
103  if (focusedApplication.stage == ApplicationInfoInterface.MainStage) {
104  mainStageAppId = focusedApplicationId;
105  priv.overlayOverride = false;
106  if (priv.startingAppId == focusedApplicationId && sideStageImage.shown) {
107  // There was already a sidestage app on top. bring it back!
108  ApplicationManager.focusApplication(priv.sideStageAppId)
109  priv.startingAppId = "";
110  }
111  } else if (focusedApplication.stage == ApplicationInfoInterface.SideStage) {
112  sideStageAppId = focusedApplicationId;
113  if (priv.startingAppId == focusedApplicationId && !sideStageImage.shown) {
114  sideStageImage.snapToApp(focusedApplication);
115  priv.startingAppId = "";
116  }
117  }
118  } else if (root.overlayMode){
119  sideStageImage.snapTo(root.width)
120  }
121  }
122 
123  onMainStageScreenshotChanged: {
124  waitingForMainScreenshot = false;
125  }
126 
127  onSideStageScreenshotChanged: {
128  waitingForSideScreenshot = false;
129  }
130 
131  onFocusedScreenshotChanged: {
132  waitingForSideScreenshot = false;
133  }
134 
135  onWaitingForScreenshotsChanged: {
136  if (waitingForScreenshots) {
137  return;
138  }
139 
140  if (root.moving) {
141  if (mainStageAppId) {
142  mainStageImage.application = mainStageApp
143  mainStageImage.visible = true;
144  }
145  if (sideStageAppId && focusedApplicationId == sideStageAppId) {
146  sideStageImage.application = sideStageApp;
147  sideStageImage.x = root.width - sideStageImage.width
148  sideStageImage.visible = true;
149  }
150  }
151  if (sideStageHandleMouseArea.pressed) {
152  if (sideStageAppId) {
153  sideStageImage.application = sideStageApp;
154  sideStageImage.x = root.width - sideStageImage.width
155  sideStageImage.visible = true;
156  }
157  if (mainStageAppId) {
158  mainStageImage.application = mainStageApp
159  mainStageImage.visible = true;
160  }
161  }
162  }
163 
164  function requestNewScreenshot(stage) {
165  if (stage == ApplicationInfoInterface.MainStage && mainStageAppId) {
166  waitingForMainScreenshot = true;
167  ApplicationManager.updateScreenshot(mainStageAppId);
168  } else if (stage == ApplicationInfoInterface.SideStage && sideStageAppId) {
169  waitingForSideScreenshot = true;
170  ApplicationManager.updateScreenshot(sideStageAppId);
171  }
172  }
173 
174  }
175  // FIXME: the signal connection seems to get lost with the fake application manager.
176  Connections {
177  target: priv.sideStageApp
178  onScreenshotChanged: priv.sideStageScreenshot = priv.sideStageApp.screenshot
179  }
180  Connections {
181  target: priv.mainStageApp
182  onScreenshotChanged: priv.mainStageScreenshot = priv.mainStageApp.screenshot
183  }
184 
185  Connections {
186  target: ApplicationManager
187 
188  onApplicationAdded: {
189  priv.startingAppId = appId;
190  splashScreenTimer.start();
191  var application = ApplicationManager.findApplication(appId)
192  if (application.stage == ApplicationInfoInterface.SideStage) {
193  sideStageSplash.visible = true;
194  } else if (application.stage == ApplicationInfoInterface.MainStage) {
195  mainStageSplash.visible = true;
196  }
197  }
198 
199  onFocusRequested: {
200  var application = ApplicationManager.findApplication(appId)
201  if (application.stage == ApplicationInfoInterface.SideStage) {
202  if (!root.shown) {
203  priv.mainStageAppId = "";
204  mainStageImage.application = null
205  }
206  if (sideStageImage.shown) {
207  sideStageImage.switchTo(application);
208  if (priv.mainStageAppId) {
209  mainStageImage.application = priv.mainStageApp;
210  mainStageImage.visible = true;
211  }
212  } else {
213  sideStageImage.application = application;
214  sideStageImage.snapToApp(application);
215  }
216  } else if (application.stage == ApplicationInfoInterface.MainStage) {
217  if (root.shown) {
218  if (sideStageImage.shown) {
219  sideStageImage.application = priv.sideStageApp;
220  sideStageImage.visible = true;
221  }
222  priv.mainStageAppId = application.appId;
223  mainStageImage.switchTo(application)
224  ApplicationManager.focusApplication(appId)
225  if (sideStageImage.shown) {
226  // There was already a focused SS app. Bring it back
227  ApplicationManager.focusApplication(priv.sideStageAppId)
228  }
229  } else {
230  if (sideStageImage.shown) {
231  sideStageImage.visible = false;
232  sideStageImage.x = root.width;
233  }
234 
235  mainStageImage.application = application;
236  ApplicationManager.focusApplication(appId)
237  }
238  }
239  }
240 
241  onApplicationRemoved: {
242  if (priv.mainStageAppId == appId) {
243  priv.mainStageAppId = "";
244  }
245  if (priv.sideStageAppId == appId) {
246  priv.sideStageAppId = "";
247  }
248  if (priv.sideStageAppId.length == 0) {
249  sideStageImage.shown = false;
250  priv.overlayOverride = false;
251  }
252  }
253 
254  }
255 
256  Timer {
257  id: splashScreenTimer
258  // FIXME: apart from removing this completely in the future and make the app surface paint
259  // meaningful stuff, also check for colin's stuff to land so we can shape 1.4 secs away from here
260  // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-manifest/+merge/210520
261  // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-pkgdir/+merge/209909
262  interval: 1700
263  repeat: false
264  onTriggered: {
265  mainStageSplash.visible = false;
266  sideStageSplash.visible = false;
267  }
268  }
269 
270  SwitchingApplicationImage {
271  id: mainStageImage
272  anchors.bottom: parent.bottom
273  width: parent.width
274  visible: false
275 
276  onSwitched: {
277  sideStageImage.visible = false;
278  }
279  }
280 
281  Rectangle {
282  id: mainStageSplash
283  anchors.fill: root
284  anchors.rightMargin: root.width - sideStageImage.x
285  color: "black"
286 
287  WaitingDots {
288  }
289  }
290 
291  SidestageHandle {
292  id: sideStageHandle
293  anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
294  width: root.dragAreaWidth
295  visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
296 
297  }
298  MouseArea {
299  id: sideStageHandleMouseArea
300  anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
301  width: root.dragAreaWidth
302  visible: priv.sideStageAppId
303 
304  property var dragPoints: new Array()
305 
306  onPressed: {
307  priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
308  if (priv.mainStageAppId) {
309  priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
310  }
311  }
312 
313  onMouseXChanged: {
314  dragPoints.push(mouseX)
315 
316  var dragPoint = root.width + mouseX;
317  if (sideStageImage.shown) {
318  dragPoint -= sideStageImage.width
319  }
320  sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
321  }
322 
323  onReleased: {
324  var distance = 0;
325  var lastX = dragPoints[0];
326  var oneWayFlick = true;
327  for (var i = 0; i < dragPoints.length; ++i) {
328  if (dragPoints[i] < lastX) {
329  oneWayFlick = false;
330  }
331  distance += dragPoints[i] - lastX;
332  lastX = dragPoints[i];
333  }
334  dragPoints = [];
335 
336  if (oneWayFlick || distance > sideStageImage.width / 2) {
337  sideStageImage.snapTo(root.width)
338  } else {
339  sideStageImage.snapToApp(priv.sideStageApp)
340  }
341  }
342  }
343 
344  SwitchingApplicationImage {
345  id: sideStageImage
346  width: priv.sideStageWidth
347  height: root.height
348  x: root.width
349  anchors.bottom: parent.bottom
350  visible: true
351  property bool shown: false
352 
353  onSwitched: {
354  mainStageImage.visible = false;
355  ApplicationManager.focusApplication(application.appId)
356  }
357 
358  function snapTo(targetX) {
359  sideStageSnapAnimation.targetX = targetX
360  sideStageImage.visible = true;
361  if (priv.mainStageAppId) {
362  mainStageImage.application = priv.mainStageApp
363  mainStageImage.visible = true;
364  }
365  sideStageSnapAnimation.start();
366  }
367 
368  function snapToApp(application) {
369  sideStageImage.application = application
370  sideStageSnapAnimation.snapToId = application.appId;
371  snapTo(root.width - sideStageImage.width);
372  }
373 
374  SequentialAnimation {
375  id: sideStageSnapAnimation
376  property int targetX: root.width
377  property string snapToId
378 
379  UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
380  ScriptAction {
381  script: {
382  if (sideStageSnapAnimation.targetX == root.width) {
383  if (priv.mainStageAppId) {
384  ApplicationManager.focusApplication(priv.mainStageAppId)
385  } else {
386  priv.overlayOverride = true;
387  ApplicationManager.unfocusCurrentApplication();
388  }
389  sideStageImage.shown = false;
390  }
391  if (sideStageSnapAnimation.snapToId) {
392  ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
393  sideStageSnapAnimation.snapToId = "";
394  sideStageImage.shown = true;
395  priv.overlayOverride = false;
396  }
397  sideStageImage.visible = false;
398  mainStageImage.visible = false;
399  }
400  }
401  }
402  }
403 
404  Rectangle {
405  id: sideStageSplash
406  anchors.fill: parent
407  anchors.leftMargin: sideStageImage.x
408  color: "black"
409 
410  WaitingDots {
411  }
412  }
413 }