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: "white"
286  }
287 
288  SidestageHandle {
289  id: sideStageHandle
290  anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
291  width: root.dragAreaWidth
292  visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
293 
294  }
295  MouseArea {
296  id: sideStageHandleMouseArea
297  anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
298  width: root.dragAreaWidth
299  visible: priv.sideStageAppId
300 
301  property var dragPoints: new Array()
302 
303  onPressed: {
304  priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
305  if (priv.mainStageAppId) {
306  priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
307  }
308  }
309 
310  onMouseXChanged: {
311  dragPoints.push(mouseX)
312 
313  var dragPoint = root.width + mouseX;
314  if (sideStageImage.shown) {
315  dragPoint -= sideStageImage.width
316  }
317  sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
318  }
319 
320  onReleased: {
321  var distance = 0;
322  var lastX = dragPoints[0];
323  var oneWayFlick = true;
324  for (var i = 0; i < dragPoints.length; ++i) {
325  if (dragPoints[i] < lastX) {
326  oneWayFlick = false;
327  }
328  distance += dragPoints[i] - lastX;
329  lastX = dragPoints[i];
330  }
331  dragPoints = [];
332 
333  if (oneWayFlick || distance > sideStageImage.width / 2) {
334  sideStageImage.snapTo(root.width)
335  } else {
336  sideStageImage.snapToApp(priv.sideStageApp)
337  }
338  }
339  }
340 
341  SwitchingApplicationImage {
342  id: sideStageImage
343  width: priv.sideStageWidth
344  height: root.height
345  x: root.width
346  anchors.bottom: parent.bottom
347  visible: true
348  property bool shown: false
349 
350  onSwitched: {
351  mainStageImage.visible = false;
352  ApplicationManager.focusApplication(application.appId)
353  }
354 
355  function snapTo(targetX) {
356  sideStageSnapAnimation.targetX = targetX
357  sideStageImage.visible = true;
358  if (priv.mainStageAppId) {
359  mainStageImage.application = priv.mainStageApp
360  mainStageImage.visible = true;
361  }
362  sideStageSnapAnimation.start();
363  }
364 
365  function snapToApp(application) {
366  sideStageImage.application = application
367  sideStageSnapAnimation.snapToId = application.appId;
368  snapTo(root.width - sideStageImage.width);
369  }
370 
371  SequentialAnimation {
372  id: sideStageSnapAnimation
373  property int targetX: root.width
374  property string snapToId
375 
376  UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
377  ScriptAction {
378  script: {
379  if (sideStageSnapAnimation.targetX == root.width) {
380  if (priv.mainStageAppId) {
381  ApplicationManager.focusApplication(priv.mainStageAppId)
382  } else {
383  priv.overlayOverride = true;
384  ApplicationManager.unfocusCurrentApplication();
385  }
386  sideStageImage.shown = false;
387  }
388  if (sideStageSnapAnimation.snapToId) {
389  ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
390  sideStageSnapAnimation.snapToId = "";
391  sideStageImage.shown = true;
392  priv.overlayOverride = false;
393  }
394  sideStageImage.visible = false;
395  mainStageImage.visible = false;
396  }
397  }
398  }
399  }
400 
401  Rectangle {
402  id: sideStageSplash
403  anchors.fill: parent
404  anchors.leftMargin: sideStageImage.x
405  color: "white"
406  }
407 }