Unity 8
TabletStage.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 Ubuntu.Gestures 0.1
20 import Unity.Application 0.1
21 import Utils 0.1
22 import Powerd 0.1
23 import "../Components"
24 
25 AbstractStage {
26  id: root
27  objectName: "stages"
28  anchors.fill: parent
29 
30  // Functions to be called from outside
31  function updateFocusedAppOrientation() {
32  var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
33  if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
34  spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
35  }
36 
37  for (var i = 0; i < spreadRepeater.count; ++i) {
38 
39  if (i === mainStageAppIndex) {
40  continue;
41  }
42 
43  var spreadDelegate = spreadRepeater.itemAt(i);
44 
45  var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
46  if (delta < 0) { delta += 360; }
47  delta = delta % 360;
48 
49  var supportedOrientations = spreadDelegate.application.supportedOrientations;
50  if (supportedOrientations === Qt.PrimaryOrientation) {
51  supportedOrientations = spreadDelegate.orientations.primary;
52  }
53 
54  if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
55  spreadDelegate.matchShellOrientation();
56  }
57  }
58  }
59  function updateFocusedAppOrientationAnimated() {
60  var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
61  if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
62  spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
63  }
64 
65  if (priv.sideStageAppId) {
66  var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
67  if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
68  spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
69  }
70  }
71  }
72 
73  orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
74 
75  onWidthChanged: {
76  spreadView.selectedIndex = -1;
77  spreadView.phase = 0;
78  spreadView.contentX = -spreadView.shift;
79  }
80 
81  onShellOrientationChanged: {
82  if (shellOrientation == Qt.PortraitOrientation || shellOrientation == Qt.InvertedPortraitOrientation) {
83  ApplicationManager.focusApplication(priv.mainStageAppId);
84  priv.sideStageAppId = "";
85  }
86  }
87 
88  onInverseProgressChanged: {
89  // This can't be a simple binding because that would be triggered after this handler
90  // while we need it active before doing the anition left/right
91  spreadView.animateX = (inverseProgress == 0)
92  if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
93  // left edge drag released. Minimum distance is given by design.
94  if (priv.oldInverseProgress > units.gu(22)) {
95  ApplicationManager.requestFocusApplication("unity8-dash");
96  }
97  }
98  priv.oldInverseProgress = inverseProgress;
99  }
100 
101  QtObject {
102  id: priv
103  objectName: "stagesPriv"
104 
105  property string focusedAppId: ApplicationManager.focusedApplicationId
106  readonly property var focusedAppDelegate: {
107  var index = indexOf(focusedAppId);
108  return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
109  }
110 
111  property string oldFocusedAppId: ""
112  property bool mainAppOrientationChangesEnabled: false
113 
114  property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
115  root.nativeHeight : root.nativeWidth
116 
117  property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
118  || root.shellOrientation === Qt.InvertedLandscapeOrientation
119 
120  property string mainStageAppId
121  property string sideStageAppId
122 
123  // For convenience, keep properties of the first two apps in the model
124  property string appId0
125  property string appId1
126 
127  property int oldInverseProgress: 0
128 
129  onFocusedAppIdChanged: {
130  if (priv.focusedAppId.length > 0) {
131  var focusedApp = ApplicationManager.findApplication(focusedAppId);
132  if (focusedApp.stage == ApplicationInfoInterface.SideStage) {
133  priv.sideStageAppId = focusedAppId;
134  } else {
135  priv.mainStageAppId = focusedAppId;
136  root.mainApp = focusedApp;
137  }
138  }
139 
140  appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
141  appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
142  }
143 
144  onFocusedAppDelegateChanged: {
145  if (focusedAppDelegate) {
146  focusedAppDelegate.focus = true;
147  }
148  }
149 
150  property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
151  (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
152  function indexOf(appId) {
153  for (var i = 0; i < ApplicationManager.count; i++) {
154  if (ApplicationManager.get(i).appId == appId) {
155  return i;
156  }
157  }
158  return -1;
159  }
160 
161  function evaluateOneWayFlick(gesturePoints) {
162  // Need to have at least 3 points to recognize it as a flick
163  if (gesturePoints.length < 3) {
164  return false;
165  }
166  // Need to have a movement of at least 2 grid units to recognize it as a flick
167  if (Math.abs(gesturePoints[gesturePoints.length - 1] - gesturePoints[0]) < units.gu(2)) {
168  return false;
169  }
170 
171  var oneWayFlick = true;
172  var smallestX = gesturePoints[0];
173  var leftWards = gesturePoints[1] < gesturePoints[0];
174  for (var i = 1; i < gesturePoints.length; i++) {
175  if ((leftWards && gesturePoints[i] >= smallestX)
176  || (!leftWards && gesturePoints[i] <= smallestX)) {
177  oneWayFlick = false;
178  break;
179  }
180  smallestX = gesturePoints[i];
181  }
182  return oneWayFlick;
183  }
184  }
185 
186  Connections {
187  target: ApplicationManager
188  onFocusRequested: {
189  if (spreadView.interactive) {
190  spreadView.snapTo(priv.indexOf(appId));
191  } else {
192  ApplicationManager.focusApplication(appId);
193  }
194  }
195 
196  onApplicationAdded: {
197  if (spreadView.phase == 2) {
198  spreadView.snapTo(ApplicationManager.count - 1);
199  } else {
200  spreadView.phase = 0;
201  spreadView.contentX = -spreadView.shift;
202  ApplicationManager.focusApplication(appId);
203  }
204  }
205 
206  onApplicationRemoved: {
207  if (priv.mainStageAppId == appId) {
208  ApplicationManager.focusApplication("unity8-dash")
209  }
210  if (priv.sideStageAppId == appId) {
211  priv.sideStageAppId = "";
212  }
213 
214  if (ApplicationManager.count == 0) {
215  spreadView.phase = 0;
216  spreadView.contentX = -spreadView.shift;
217  } else if (spreadView.closingIndex == -1) {
218  // Unless we're closing the app ourselves in the spread,
219  // lets make sure the spread doesn't mess up by the changing app list.
220  spreadView.phase = 0;
221  spreadView.contentX = -spreadView.shift;
222  ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
223  }
224  }
225  }
226 
227  Flickable {
228  id: spreadView
229  objectName: "spreadView"
230  anchors.fill: parent
231  interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
232  contentWidth: spreadRow.width - shift
233  contentX: -shift
234 
235  property int tileDistance: units.gu(20)
236  property int sideStageWidth: units.gu(40)
237  property bool sideStageVisible: priv.sideStageAppId
238 
239  // This indicates when the spreadView is active. That means, all the animations
240  // are activated and tiles need to line up for the spread.
241  readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
242 
243  // The flickable needs to fill the screen in order to get touch events all over.
244  // However, we don't want to the user to be able to scroll back all the way. For
245  // that, the beginning of the gesture starts with a negative value for contentX
246  // so the flickable wants to pull it into the view already. "shift" tunes the
247  // distance where to "lock" the content.
248  readonly property real shift: width / 2
249  readonly property real shiftedContentX: contentX + shift
250 
251  // Phase of the animation:
252  // 0: Starting from right edge, a new app (index 1) comes in from the right
253  // 1: The app has reached the first snap position.
254  // 2: The list is dragged further and snaps into the spread view when entering phase 2
255  property int phase
256 
257  readonly property int phase0Width: sideStageWidth
258  readonly property int phase1Width: sideStageWidth
259 
260  // Those markers mark the various positions in the spread (ratio to screen width from right to left):
261  // 0 - 1: following finger, snap back to the beginning on release
262  readonly property real positionMarker1: 0.2
263  // 1 - 2: curved snapping movement, snap to nextInStack on release
264  readonly property real positionMarker2: sideStageWidth / spreadView.width
265  // 2 - 3: movement follows finger, snaps to phase 2 (full spread) on release
266  readonly property real positionMarker3: 0.6
267  // passing 3, we detach movement from the finger and snap to phase 2 (full spread)
268  readonly property real positionMarker4: 0.8
269 
270  readonly property int startSnapPosition: phase0Width * 0.5
271  readonly property int endSnapPosition: phase0Width * 0.75
272  readonly property real snapPosition: 0.75
273 
274  property int selectedIndex: -1
275  property int draggedDelegateCount: 0
276  property int closingIndex: -1
277 
278  // FIXME: Workaround Flickable's not keepping its contentX still when resized
279  onContentXChanged: { forceItToRemainStillIfBeingResized(); }
280  onShiftChanged: { forceItToRemainStillIfBeingResized(); }
281  function forceItToRemainStillIfBeingResized() {
282  if (root.beingResized && contentX != -shift) {
283  contentX = -shift;
284  }
285  }
286 
287  property bool animateX: true
288  property bool beingResized: root.beingResized
289  onBeingResizedChanged: {
290  if (beingResized) {
291  // Brace yourselves for impact!
292  selectedIndex = -1;
293  phase = 0;
294  contentX = -shift;
295  }
296  }
297 
298  property bool sideStageDragging: sideStageDragHandle.dragging
299  property real sideStageDragProgress: sideStageDragHandle.progress
300 
301  onSideStageDragProgressChanged: {
302  if (sideStageDragProgress == 1) {
303  ApplicationManager.focusApplication(priv.mainStageAppId);
304  priv.sideStageAppId = "";
305  }
306  }
307 
308  // In case the ApplicationManager already holds an app when starting up we're missing animations
309  // Make sure we end up in the same state
310  Component.onCompleted: {
311  spreadView.contentX = -spreadView.shift
312  }
313 
314  property int nextInStack: {
315  switch (state) {
316  case "main":
317  if (ApplicationManager.count > 1) {
318  return 1;
319  }
320  return -1;
321  case "mainAndOverlay":
322  if (ApplicationManager.count <= 2) {
323  return -1;
324  }
325  if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
326  if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
327  return 2;
328  }
329  return 1;
330  }
331  return 0;
332  case "overlay":
333  return 1;
334  }
335  return -1;
336  }
337  property int nextZInStack: indexToZIndex(nextInStack)
338 
339  states: [
340  State {
341  name: "empty"
342  },
343  State {
344  name: "main"
345  },
346  State { // Side Stage only in overlay mode
347  name: "overlay"
348  },
349  State { // Main Stage and Side Stage in overlay mode
350  name: "mainAndOverlay"
351  },
352  State { // Main Stage and Side Stage in split mode
353  name: "mainAndSplit"
354  }
355  ]
356  state: {
357  if (priv.mainStageAppId && !priv.sideStageAppId) {
358  return "main";
359  }
360  if (!priv.mainStageAppId && priv.sideStageAppId) {
361  return "overlay";
362  }
363  if (priv.mainStageAppId && priv.sideStageAppId) {
364  return "mainAndOverlay";
365  }
366  return "empty";
367  }
368 
369  onShiftedContentXChanged: {
370  if (root.beingResized) {
371  // Flickabe.contentX wiggles during resizes. Don't react to it.
372  return;
373  }
374  if (spreadView.phase == 0 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker2) {
375  spreadView.phase = 1;
376  } else if (spreadView.phase == 1 && spreadView.shiftedContentX > spreadView.width * spreadView.positionMarker4) {
377  spreadView.phase = 2;
378  } else if (spreadView.phase == 1 && spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
379  spreadView.phase = 0;
380  }
381  }
382 
383  function snap() {
384  if (shiftedContentX < phase0Width) {
385  snapAnimation.targetContentX = -shift;
386  snapAnimation.start();
387  } else if (shiftedContentX < phase1Width) {
388  snapTo(1);
389  } else {
390  // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
391  snapAnimation.targetContentX = spreadView.width * spreadView.positionMarker4 + 1 - shift;
392  snapAnimation.start();
393  }
394  }
395  function snapTo(index) {
396  spreadView.selectedIndex = index;
397  snapAnimation.targetContentX = -shift;
398  snapAnimation.start();
399  }
400 
401  // We need to shuffle z ordering a bit in order to keep side stage apps above main stage apps.
402  // We don't want to really reorder them in the model because that allows us to keep track
403  // of the last focused order.
404  function indexToZIndex(index) {
405  var app = ApplicationManager.get(index);
406  if (!app) {
407  return index;
408  }
409 
410  var active = app.appId == priv.mainStageAppId || app.appId == priv.sideStageAppId;
411  if (active && app.stage == ApplicationInfoInterface.MainStage) {
412  // if this app is active, and its the MainStage, always put it to index 0
413  return 0;
414  }
415  if (active && app.stage == ApplicationInfoInterface.SideStage) {
416  if (!priv.mainStageAppId) {
417  // Only have SS apps running. Put the active one at 0
418  return 0;
419  }
420 
421  // Precondition now: There's an active MS app and this is SS app:
422  if (spreadView.nextInStack >= 0 && ApplicationManager.get(spreadView.nextInStack).stage == ApplicationInfoInterface.MainStage) {
423  // If the next app coming from the right is a MS app, we need to elevate this SS ap above it.
424  // Put it to at least level 2, or higher if there's more apps coming in before this one.
425  return Math.max(index, 2);
426  } else {
427  // if this is no next app to come in from the right, place this one at index 1, just on top the active MS app.
428  return 1;
429  }
430  }
431  if (index <= 2 && app.stage == ApplicationInfoInterface.MainStage && priv.sideStageAppId) {
432  // Ok, this is an inactive MS app. If there's an active SS app around, we need to place this one
433  // in between the active MS app and the active SS app, so that it comes in from there when dragging from the right.
434  // If there's now active SS app, just leave it where it is.
435  return priv.indexOf(priv.sideStageAppId) < index ? index - 1 : index;
436  }
437  if (index == spreadView.nextInStack && app.stage == ApplicationInfoInterface.SideStage) {
438  // This is a SS app and the next one to come in from the right:
439  if (priv.sideStageAppId && priv.mainStageAppId) {
440  // If there's both, an active MS and an active SS app, put this one right on top of that
441  return 2;
442  }
443  // Or if there's only one other active app, put it on top of that.
444  // The case that there isn't any other active app is already handled above.
445  return 1;
446  }
447  if (index == 2 && spreadView.nextInStack == 1 && priv.sideStageAppId) {
448  // If its index 2 but not the next one to come in, it means
449  // we've pulled another one down to index 2. Move this one up to 2 instead.
450  return 3;
451  }
452  // don't touch all others... (mostly index > 3 + simple cases where the above doesn't shuffle much)
453  return index;
454  }
455 
456  SequentialAnimation {
457  id: snapAnimation
458  property int targetContentX: -spreadView.shift
459 
460  UbuntuNumberAnimation {
461  target: spreadView
462  property: "contentX"
463  to: snapAnimation.targetContentX
464  duration: UbuntuAnimation.FastDuration
465  }
466 
467  ScriptAction {
468  script: {
469  if (spreadView.selectedIndex >= 0) {
470  var newIndex = spreadView.selectedIndex;
471  spreadView.selectedIndex = -1;
472  ApplicationManager.focusApplication(ApplicationManager.get(newIndex).appId);
473  spreadView.phase = 0;
474  spreadView.contentX = -spreadView.shift;
475  }
476  }
477  }
478  }
479 
480  MouseArea {
481  id: spreadRow
482  x: spreadView.contentX
483  width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
484  height: root.height
485 
486  onClicked: {
487  spreadView.snapTo(0);
488  }
489 
490  Rectangle {
491  id: sideStageBackground
492  color: "black"
493  width: spreadView.sideStageWidth * (1 - sideStageDragHandle.progress)
494  height: priv.landscapeHeight
495  x: spreadView.width - width
496  z: spreadView.indexToZIndex(priv.indexOf(priv.sideStageAppId))
497  opacity: spreadView.phase == 0 ? 1 : 0
498  Behavior on opacity { UbuntuNumberAnimation {} }
499  }
500 
501  Item {
502  id: sideStageDragHandle
503  anchors.right: sideStageBackground.left
504  anchors.top: sideStageBackground.top
505  width: units.gu(2)
506  height: priv.landscapeHeight
507  z: sideStageBackground.z
508  opacity: spreadView.phase <= 0 && spreadView.sideStageVisible ? 1 : 0
509  property real progress: 0
510  property bool dragging: false
511 
512  Behavior on opacity { UbuntuNumberAnimation {} }
513 
514  Connections {
515  target: spreadView
516  onSideStageVisibleChanged: {
517  if (spreadView.sideStageVisible) {
518  sideStageDragHandle.progress = 0;
519  }
520  }
521  }
522 
523  Image {
524  anchors.centerIn: parent
525  width: sideStageDragHandleMouseArea.pressed ? parent.width * 2 : parent.width
526  height: parent.height
527  source: "graphics/sidestage_handle@20.png"
528  Behavior on width { UbuntuNumberAnimation {} }
529  }
530 
531  MouseArea {
532  id: sideStageDragHandleMouseArea
533  anchors.fill: parent
534  enabled: spreadView.shiftedContentX == 0
535  property int startX
536  property var gesturePoints: new Array()
537  property real totalDiff
538 
539  onPressed: {
540  gesturePoints = [];
541  startX = mouseX;
542  totalDiff = 0.0;
543  sideStageDragHandle.progress = 0;
544  sideStageDragHandle.dragging = true;
545  }
546  onMouseXChanged: {
547  totalDiff += mouseX - startX;
548  if (priv.mainStageAppId) {
549  sideStageDragHandle.progress = Math.max(0, totalDiff / spreadView.sideStageWidth);
550  }
551  gesturePoints.push(mouseX);
552  }
553  onReleased: {
554  if (priv.mainStageAppId) {
555  var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
556  sideStageDragSnapAnimation.to = sideStageDragHandle.progress > 0.5 || oneWayFlick ? 1 : 0;
557  sideStageDragSnapAnimation.start();
558  } else {
559  sideStageDragHandle.dragging = false;
560  }
561  }
562  }
563  UbuntuNumberAnimation {
564  id: sideStageDragSnapAnimation
565  target: sideStageDragHandle
566  property: "progress"
567 
568  onRunningChanged: {
569  if (!running) {
570  sideStageDragHandle.dragging = false;
571  }
572  }
573  }
574  }
575 
576  Repeater {
577  id: spreadRepeater
578  objectName: "spreadRepeater"
579  model: ApplicationManager
580 
581  delegate: TransformedTabletSpreadDelegate {
582  id: spreadTile
583  objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
584  : "tabletSpreadDelegate_null";
585  width: {
586  if (wantsMainStage) {
587  return spreadView.width;
588  } else {
589  return spreadView.sideStageWidth;
590  }
591  }
592  height: {
593  if (wantsMainStage) {
594  return spreadView.height;
595  } else {
596  return priv.landscapeHeight;
597  }
598  }
599  active: model.appId == priv.mainStageAppId || model.appId == priv.sideStageAppId
600  zIndex: spreadView.indexToZIndex(index)
601  selected: spreadView.selectedIndex == index
602  otherSelected: spreadView.selectedIndex >= 0 && !selected
603  isInSideStage: priv.sideStageAppId == model.appId
604  interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
605  swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
606  maximizedAppTopMargin: root.maximizedAppTopMargin
607  dragOffset: !isDash && model.appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
608  application: ApplicationManager.get(index)
609  closeable: !isDash
610 
611  readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
612 
613  readonly property bool isDash: model.appId == "unity8-dash"
614 
615  readonly property bool canSuspend: model.isTouchApp
616  && !isExemptFromLifecycle(model.appId)
617 
618  Binding {
619  target: spreadTile.application
620  property: "requestedState"
621  value: !canSuspend
622  || (isDash && root.keepDashRunning)
623  || (!root.suspended && (model.appId == priv.mainStageAppId
624  || model.appId == priv.sideStageAppId))
625  ? ApplicationInfoInterface.RequestedRunning
626  : ApplicationInfoInterface.RequestedSuspended
627  }
628 
629  // FIXME: A regular binding doesn't update any more after closing an app.
630  // Using a Binding for now.
631  Binding {
632  target: spreadTile
633  property: "z"
634  value: (!spreadView.active && isDash && !active) ? -1 : spreadTile.zIndex
635  }
636  x: spreadView.width
637 
638  property real behavioredZIndex: zIndex
639  Behavior on behavioredZIndex {
640  enabled: spreadView.closingIndex >= 0
641  UbuntuNumberAnimation {}
642  }
643 
644  // This is required because none of the bindings are triggered in some cases:
645  // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
646  // returns a different app even though the nextInStackIndex and all the related
647  // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
648  // binding update in that case.
649  Connections {
650  target: ApplicationManager
651  onApplicationRemoved: spreadTile.z = Qt.binding(function() {
652  return spreadView.indexToZIndex(index);
653  })
654  }
655 
656  progress: {
657  var tileProgress = (spreadView.shiftedContentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
658  // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
659  if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
660  tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
661  }
662  return tileProgress;
663  }
664 
665  // TODO: Hiding tile when progress is such that it will be off screen.
666  property bool occluded: {
667  if (spreadView.active) return false;
668  else if (spreadTile.active) return false;
669  else if (xTranslateAnimating) return false;
670  else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
671  return true;
672  }
673 
674  visible: Powerd.status == Powerd.On &&
675  !greeter.fullyShown &&
676  !occluded
677 
678  animatedProgress: {
679  if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
680  if (progress < spreadView.positionMarker1) {
681  return progress;
682  } else if (progress < spreadView.positionMarker1 + snappingCurve.period) {
683  return spreadView.positionMarker1 + snappingCurve.value * 3;
684  } else {
685  return spreadView.positionMarker2;
686  }
687  }
688  return progress;
689  }
690 
691  shellOrientationAngle: wantsMainStage ? root.shellOrientationAngle : 0
692  shellOrientation: wantsMainStage ? root.shellOrientation : Qt.PortraitOrientation
693  orientations: Orientations {
694  primary: spreadTile.wantsMainStage ? root.orientations.primary : Qt.PortraitOrientation
695  native_: spreadTile.wantsMainStage ? root.orientations.native_ : Qt.PortraitOrientation
696  portrait: root.orientations.portrait
697  invertedPortrait: root.orientations.invertedPortrait
698  landscape: root.orientations.landscape
699  invertedLandscape: root.orientations.invertedLandscape
700  }
701 
702  onClicked: {
703  if (spreadView.phase == 2) {
704  spreadView.snapTo(index);
705  }
706  }
707 
708  onDraggedChanged: {
709  if (dragged) {
710  spreadView.draggedDelegateCount++;
711  } else {
712  spreadView.draggedDelegateCount--;
713  }
714  }
715 
716  onClosed: {
717  spreadView.closingIndex = index;
718  ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
719  }
720 
721  Binding {
722  target: root
723  when: model.appId == priv.mainStageAppId
724  property: "mainAppWindowOrientationAngle"
725  value: appWindowOrientationAngle
726  }
727  Binding {
728  target: priv
729  when: model.appId == priv.mainStageAppId
730  property: "mainAppOrientationChangesEnabled"
731  value: orientationChangesEnabled
732  }
733 
734  EasingCurve {
735  id: snappingCurve
736  type: EasingCurve.Linear
737  period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
738  progress: spreadTile.progress - spreadView.positionMarker1
739  }
740  }
741  }
742  }
743  }
744 
745  //eat touch events during the right edge gesture
746  MouseArea {
747  anchors.fill: parent
748  enabled: spreadDragArea.dragging
749  }
750 
751  DirectionalDragArea {
752  id: spreadDragArea
753  objectName: "spreadDragArea"
754  anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
755  width: root.dragAreaWidth
756  direction: Direction.Leftwards
757  enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
758 
759  property var gesturePoints: new Array()
760 
761  onTouchXChanged: {
762  if (!dragging) {
763  spreadView.phase = 0;
764  spreadView.contentX = -spreadView.shift;
765  }
766 
767  if (dragging) {
768  var dragX = -touchX + spreadDragArea.width - spreadView.shift;
769  var maxDrag = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
770  spreadView.contentX = Math.min(dragX, maxDrag);
771  }
772  gesturePoints.push(touchX);
773  }
774 
775  onDraggingChanged: {
776  if (dragging) {
777  // Gesture recognized. Start recording this gesture
778  gesturePoints = [];
779  } else {
780  // Ok. The user released. Find out if it was a one-way movement.
781  var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
782  gesturePoints = [];
783 
784  if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
785  // If it was a short one-way movement, do the Alt+Tab switch
786  // no matter if we didn't cross positionMarker1 yet.
787  spreadView.snapTo(spreadView.nextInStack);
788  } else {
789  if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
790  spreadView.snap();
791  } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
792  spreadView.snapTo(spreadView.nextInStack);
793  } else {
794  // otherwise snap to the closest snap position we can find
795  // (might be back to start, to app 1 or to spread)
796  spreadView.snap();
797  }
798  }
799  }
800  }
801  }
802 }