2 * Copyright (C) 2014-2016 Canonical, Ltd.
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.
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.
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/>.
18 import Ubuntu.Components 1.3
19 import Ubuntu.Gestures 0.1
20 import Unity.Application 0.1
23 import "../Components"
30 // <tutorial-hacks> The Tutorial looks into our implementation details
31 property alias sideStageVisible: spreadView.sideStageVisible
32 property alias sideStageWidth: spreadView.sideStageWidth
33 // The stage the currently focused surface is in
34 property int stageFocusedSurface: priv.focusedAppDelegate ? priv.focusedAppDelegate.stage : ApplicationInfoInterface.MainStage
37 paintBackground: spreadView.shiftedContentX !== 0
39 // Functions to be called from outside
40 function updateFocusedAppOrientation() {
41 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
43 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
44 spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
47 for (var i = 0; i < spreadRepeater.count; ++i) {
49 if (i === mainStageIndex) {
53 var spreadDelegate = spreadRepeater.itemAt(i);
55 var delta = spreadDelegate.appWindowOrientationAngle - root.shellOrientationAngle;
56 if (delta < 0) { delta += 360; }
59 var supportedOrientations = spreadDelegate.supportedOrientations;
60 if (supportedOrientations === Qt.PrimaryOrientation) {
61 supportedOrientations = spreadDelegate.orientations.primary;
64 if (delta === 180 && (supportedOrientations & spreadDelegate.shellOrientation)) {
65 spreadDelegate.matchShellOrientation();
69 function updateFocusedAppOrientationAnimated() {
70 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
71 if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
72 spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
75 var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
76 if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
77 spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
81 function pushRightEdge(amount) {
82 if (spreadView.contentX == -spreadView.shift) {
83 edgeBarrier.push(amount);
87 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
90 if (priv.mainStageItemId > 0) {
91 var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
92 return root.topLevelSurfaceList.applicationAt(index);
98 supportedOrientations: {
100 var orientations = mainApp.supportedOrientations;
101 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
102 if (priv.sideStageItemId && !spreadView.surfaceDragging) {
103 // If we have a sidestage app, support Portrait orientation
104 // so that it will switch the sidestage app to mainstage on rotate
105 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
109 // we just don't care
110 return Qt.PortraitOrientation |
111 Qt.LandscapeOrientation |
112 Qt.InvertedPortraitOrientation |
113 Qt.InvertedLandscapeOrientation;
117 // How far left the stage has been dragged, used externally by tutorial code
118 dragProgress: spreadRepeater.count > 0 ? spreadRepeater.itemAt(0).animatedProgress : 0
121 spreadView.selectedIndex = -1;
122 spreadView.phase = 0;
123 spreadView.contentX = -spreadView.shift;
126 onInverseProgressChanged: {
127 // This can't be a simple binding because that would be triggered after this handler
128 // while we need it active before doing the anition left/right
129 spreadView.animateX = (inverseProgress == 0)
130 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
131 // left edge drag released. Minimum distance is given by design.
132 if (priv.oldInverseProgress > units.gu(22)) {
133 root.applicationManager.requestFocusApplication("unity8-dash");
136 priv.oldInverseProgress = inverseProgress;
139 onAltTabPressedChanged: {
140 if (!spreadEnabled) {
144 priv.highlightIndex = Math.min(spreadRepeater.count - 1, 1);
145 spreadView.snapToSpread();
147 for (var i = 0; i < spreadRepeater.count; i++) {
148 if (spreadRepeater.itemAt(i).zIndex === priv.highlightIndex) {
149 spreadView.snapTo(i);
157 focus: root.altTabPressed
162 priv.highlightIndex = (priv.highlightIndex + 1) % spreadRepeater.count
165 priv.highlightIndex = (priv.highlightIndex + spreadRepeater.count - 1) % spreadRepeater.count
172 target: root.topLevelSurfaceList
173 onListChanged: priv.updateMainAndSideStageIndexes()
178 objectName: "stagesPriv"
180 function updateMainAndSideStageIndexes() {
181 var choseMainStage = false;
182 var choseSideStage = false;
184 if (!root.topLevelSurfaceList)
187 for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
188 var spreadDelegate = spreadRepeater.itemAt(i);
189 if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
190 && !choseSideStage) {
191 priv.sideStageDelegate = spreadDelegate
192 priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
193 priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
194 choseSideStage = true;
195 } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
196 priv.mainStageDelegate = spreadDelegate;
197 priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
198 priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
199 choseMainStage = true;
202 if (!choseMainStage) {
203 priv.mainStageDelegate = null;
204 priv.mainStageItemId = 0;
205 priv.mainStageAppId = "";
207 if (!choseSideStage) {
208 priv.sideStageDelegate = null;
209 priv.sideStageItemId = 0;
210 priv.sideStageAppId = "";
214 property var focusedAppDelegate: null
216 property bool mainAppOrientationChangesEnabled: false
218 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
219 root.nativeHeight : root.nativeWidth
221 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
222 || root.shellOrientation === Qt.InvertedLandscapeOrientation
224 property var mainStageDelegate: null
225 property var sideStageDelegate: null
227 property int mainStageItemId: 0
228 property int sideStageItemId: 0
230 property string mainStageAppId: ""
231 property string sideStageAppId: ""
233 property int oldInverseProgress: 0
235 property int highlightIndex: 0
237 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
238 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
239 function evaluateOneWayFlick(gesturePoints) {
240 // Need to have at least 3 points to recognize it as a flick
241 if (gesturePoints.length < 3) {
244 // Need to have a movement of at least 2 grid units to recognize it as a flick
245 if (Math.abs(gesturePoints[gesturePoints.length - 1] - gesturePoints[0]) < units.gu(2)) {
249 var oneWayFlick = true;
250 var smallestX = gesturePoints[0];
251 var leftWards = gesturePoints[1] < gesturePoints[0];
252 for (var i = 1; i < gesturePoints.length; i++) {
253 if ((leftWards && gesturePoints[i] >= smallestX)
254 || (!leftWards && gesturePoints[i] <= smallestX)) {
258 smallestX = gesturePoints[i];
263 onHighlightIndexChanged: {
264 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
267 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
268 root.shellOrientation == Qt.InvertedLandscapeOrientation
272 model: root.applicationManager
274 property var stateBinding: Binding {
275 readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
276 target: model.application
277 property: "requestedState"
279 // NB: the first application clause is just to ensure we never get warnings for trying to access
280 // members of a null variable.
281 value: model.application &&
283 (isDash && root.keepDashRunning)
284 || (!root.suspended && (model.application.appId === priv.mainStageAppId
285 || model.application.appId === priv.sideStageAppId))
287 ? ApplicationInfoInterface.RequestedRunning
288 : ApplicationInfoInterface.RequestedSuspended
291 property var lifecycleBinding: Binding {
292 target: model.application
293 property: "exemptFromLifecycle"
294 value: model.application
295 ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
302 target: MirFocusController
303 property: "focusedSurface"
304 value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
305 when: root.parent && !spreadRepeater.startingUp
310 objectName: "spreadView"
312 interactive: (spreadDragArea.dragging || phase > 1) && draggedDelegateCount === 0
313 contentWidth: spreadRow.width - shift
316 property int tileDistance: units.gu(20)
318 // This indicates when the spreadView is active. That means, all the animations
319 // are activated and tiles need to line up for the spread.
320 readonly property bool active: shiftedContentX > 0 || spreadDragArea.dragging
322 // The flickable needs to fill the screen in order to get touch events all over.
323 // However, we don't want to the user to be able to scroll back all the way. For
324 // that, the beginning of the gesture starts with a negative value for contentX
325 // so the flickable wants to pull it into the view already. "shift" tunes the
326 // distance where to "lock" the content.
327 readonly property real shift: width / 2
328 readonly property real shiftedContentX: contentX + shift
330 // Phase of the animation:
331 // 0: Starting from right edge, a new app (index 1) comes in from the right
332 // 1: The app has reached the first snap position.
333 // 2: The list is dragged further and snaps into the spread view when entering phase 2
336 readonly property int phase0Width: sideStageWidth
337 readonly property int phase1Width: sideStageWidth
339 // Those markers mark the various positions in the spread (ratio to screen width from right to left):
340 // 0 - 1: following finger, snap back to the beginning on release
341 readonly property real positionMarker1: 0.2
342 // 1 - 2: curved snapping movement, snap to nextInStack on release
343 readonly property real positionMarker2: sideStageWidth / spreadView.width
344 // 2 - 3: movement follows finger, snaps to phase 2 (full spread) on release
345 readonly property real positionMarker3: 0.6
346 // passing 3, we detach movement from the finger and snap to phase 2 (full spread)
347 readonly property real positionMarker4: 0.8
349 readonly property int startSnapPosition: phase0Width * 0.5
350 readonly property int endSnapPosition: phase0Width * 0.75
351 readonly property real snapPosition: 0.75
353 property int selectedIndex: -1
354 property int draggedDelegateCount: 0
355 property int closingIndex: -1
356 property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
358 // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
359 // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
360 // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
361 // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
362 // the Flickable.boundsBehavior upon release).
364 if (!undoContentXReset()) {
365 forceItToRemainStillIfBeingResized();
368 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
369 function forceItToRemainStillIfBeingResized() {
370 if (root.beingResized && contentX != -spreadView.shift) {
371 contentX = -spreadView.shift;
374 function undoContentXReset() {
375 if (contentWidth <= 0) {
376 contentWidthOnLastContentXChange = contentWidth;
377 lastContentX = contentX;
381 if (contentWidth != contentWidthOnLastContentXChange
382 && lastContentX == -shift && contentX == 0) {
383 // Flickable is resetting contentX because contentWidth has changed. Undo it.
388 contentWidthOnLastContentXChange = contentWidth;
389 lastContentX = contentX;
392 property real contentWidthOnLastContentXChange: -1
393 property real lastContentX: 0
396 property bool animateX: true
397 property bool beingResized: root.beingResized
398 onBeingResizedChanged: {
400 // Brace yourselves for impact!
407 property real sideStageWidth: units.gu(40)
409 property bool surfaceDragging: triGestureArea.recognisedDrag
411 readonly property bool sideStageVisible: priv.sideStageItemId != 0
413 // In case applicationManager already holds an app when starting up we're missing animations
414 // Make sure we end up in the same state
415 Component.onCompleted: {
416 spreadView.contentX = -spreadView.shift
419 property int nextInStack: {
420 var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
421 var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
424 if (root.topLevelSurfaceList.count > 1) {
428 case "mainAndOverlay":
429 if (root.topLevelSurfaceList.count <= 2) {
432 if (mainStageIndex == 0 || sideStageIndex == 0) {
433 if (mainStageIndex == 1 || sideStageIndex == 1) {
444 property int nextZInStack
453 State { // Side Stage only in overlay mode
456 State { // Main Stage and Side Stage in overlay mode
457 name: "mainAndOverlay"
459 State { // Main Stage and Side Stage in split mode
464 if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
467 if (!priv.mainStageItemId && priv.sideStageItemId) {
470 if (priv.mainStageItemId && priv.sideStageItemId) {
471 return "mainAndOverlay";
476 onShiftedContentXChanged: {
477 if (root.beingResized) {
478 // Flickabe.contentX wiggles during resizes. Don't react to it.
484 // the "spreadEnabled" part is because when code does "phase = 0; contentX = -shift" to
485 // dismiss the spread because spreadEnabled went to false, for some reason, during tests,
486 // Flickable might jump in and change contentX value back, causing the code below to do
487 // "phase = 1" which will make the spread stay.
488 // It sucks that we have no control whatsoever over whether or when Flickable animates its
490 if (root.spreadEnabled && shiftedContentX > width * positionMarker2) {
495 if (shiftedContentX < width * positionMarker2) {
497 } else if (shiftedContentX >= width * positionMarker4 && !spreadDragArea.dragging) {
505 if (shiftedContentX < phase0Width) {
506 snapAnimation.targetContentX = -shift;
507 snapAnimation.start();
508 } else if (shiftedContentX < phase1Width) {
515 function snapToSpread() {
516 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
517 snapAnimation.targetContentX = (spreadView.width * spreadView.positionMarker4) + 1 - shift;
518 snapAnimation.start();
521 function snapTo(index) {
522 snapAnimation.stop();
523 spreadView.selectedIndex = index;
524 snapAnimation.targetContentX = -shift;
525 snapAnimation.start();
528 // We need to shuffle z ordering a bit in order to keep side stage apps above main stage apps.
529 // We don't want to really reorder them in the model because that allows us to keep track
530 // of the last focused order.
531 function indexToZIndex(index) {
532 // only shuffle when we've got a main and overlay
533 if (state !== "mainAndOverlay") return index;
535 var app = root.topLevelSurfaceList.applicationAt(index);
539 var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : app.stage;
541 // don't shuffle indexes greater than "actives or next"
542 if (index > 2) return index;
544 var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
546 if (index == mainStageIndex) {
547 // Active main stage always at 0
551 if (spreadView.nextInStack > 0) {
552 var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
554 if (index === spreadView.nextInStack) {
555 // this is the next app in stack.
557 if (stage === ApplicationInfoInterface.SideStage) {
558 // if the next app in stack is a sidestage app, it must order on top of other side stage app
559 return Math.min(2, root.topLevelSurfaceList.count-1);
563 if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
564 // if the next app in stack is a sidestage app, it must order on top of other side stage app
567 return Math.min(2, root.topLevelSurfaceList.count-1);
569 return Math.min(index+1, root.topLevelSurfaceList.count-1);
572 SequentialAnimation {
574 property int targetContentX: -spreadView.shift
576 UbuntuNumberAnimation {
579 to: snapAnimation.targetContentX
580 duration: UbuntuAnimation.FastDuration
585 if (spreadView.selectedIndex >= 0) {
586 var newIndex = spreadView.selectedIndex;
587 var application = root.topLevelSurfaceList.applicationAt(newIndex);
588 var spreadDelegate = spreadRepeater.itemAt(newIndex);
589 if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
592 spreadView.selectedIndex = -1;
593 spreadDelegate.focus = true;
594 spreadView.phase = 0;
595 spreadView.contentX = -spreadView.shift;
601 Behavior on contentX {
602 enabled: root.altTabPressed
603 UbuntuNumberAnimation {}
608 x: spreadView.contentX
609 width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
613 spreadView.snapTo(0);
617 objectName: "MainStageDropArea"
621 bottom: parent.bottom
623 width: spreadView.width - sideStage.width
624 enabled: priv.sideStageEnabled
627 drop.source.spreadDelegate.stage = ApplicationInfoInterface.MainStage;
628 drop.source.spreadDelegate.focus = true;
635 objectName: "sideStage"
636 height: priv.landscapeHeight
637 x: spreadView.width - width
639 if (!priv.mainStageItemId) return 0;
641 if (priv.sideStageItemId && spreadView.nextInStack > 0) {
642 var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
644 if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
645 // if the next app in stack is a main stage app, put the sidestage on top of it.
653 visible: progress != 0
654 enabled: priv.sideStageEnabled && sideStageDropArea.dropAllowed
655 opacity: priv.sideStageEnabled && !spreadView.active ? 1 : 0
656 Behavior on opacity { UbuntuNumberAnimation {} }
659 if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
660 && priv.mainStageDelegate) {
661 priv.mainStageDelegate.focus = true;
662 } else if (shown && priv.sideStageDelegate) {
663 priv.sideStageDelegate.focus = true;
668 id: sideStageDropArea
669 objectName: "SideStageDropArea"
672 property bool dropAllowed: true
675 dropAllowed = drag.keys != "Disabled";
681 if (drop.keys == "MainStage") {
682 drop.source.spreadDelegate.stage = ApplicationInfoInterface.SideStage;
683 drop.source.spreadDelegate.focus = true;
688 if (!sideStageDropArea.drag.source) {
696 TopLevelSurfaceRepeater {
698 objectName: "spreadRepeater"
699 model: root.topLevelSurfaceList
702 priv.updateMainAndSideStageIndexes();
703 if (spreadView.phase == 2) {
704 spreadView.snapTo(index);
709 priv.updateMainAndSideStageIndexes();
710 // Unless we're closing the app ourselves in the spread,
711 // lets make sure the spread doesn't mess up by the changing app list.
712 if (spreadView.closingIndex == -1) {
713 spreadView.phase = 0;
714 spreadView.contentX = -spreadView.shift;
718 function focusTopMostApp() {
719 if (spreadRepeater.count > 0) {
720 var topmostDelegate = spreadRepeater.itemAt(0);
721 topmostDelegate.focus = true;
725 delegate: TransformedTabletSpreadDelegate {
727 objectName: "spreadDelegate_" + model.id
729 readonly property int index: model.index
730 width: spreadView.width
731 height: spreadView.height
732 active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
733 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
735 if (spreadView.nextInStack == model.index) {
736 spreadView.nextZInStack = zIndex;
739 selected: spreadView.selectedIndex == index
740 otherSelected: spreadView.selectedIndex >= 0 && !selected
741 isInSideStage: priv.sideStageItemId == model.id
742 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
743 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
744 maximizedAppTopMargin: root.maximizedAppTopMargin
745 dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
746 && spreadView.phase === 0 ? root.inverseProgress : 0
747 application: model.application
748 surface: model.surface
750 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
751 dropShadow: spreadView.active || priv.focusedAppDelegateIsDislocated
753 readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
755 readonly property bool isDash: application.appId == "unity8-dash"
758 if (focus && !spreadRepeater.startingUp) {
759 priv.focusedAppDelegate = spreadTile;
760 root.topLevelSurfaceList.raiseId(model.id);
762 if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
767 target: model.surface
768 onFocusRequested: spreadTile.focus = true;
771 target: spreadTile.application
773 if (!model.surface) {
774 // when an app has no surfaces, we assume there's only one entry representing it:
776 spreadTile.focus = true;
778 // if the application has surfaces, focus request should be at surface-level.
784 if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
785 return priv.mainStageDelegate.fullscreen;
786 } else if (surface) {
787 return surface.state === Mir.FullscreenState;
788 } else if (application) {
789 return application.fullscreen;
795 supportedOrientations: {
797 var orientations = application.supportedOrientations;
798 if (stage == ApplicationInfoInterface.MainStage) {
799 // When an app is in the mainstage, it always supports Landscape|InvertedLandscape
800 // so that we can drag it from the main stage to the side stage
801 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
805 // we just don't care
806 return Qt.PortraitOrientation |
807 Qt.LandscapeOrientation |
808 Qt.InvertedPortraitOrientation |
809 Qt.InvertedLandscapeOrientation;
813 // FIXME: A regular binding doesn't update any more after closing an app.
814 // Using a Binding for now.
818 value: (!spreadView.active && isDash && !active) ? -1 : spreadTile.zIndex
822 property real behavioredZIndex: zIndex
823 Behavior on behavioredZIndex {
824 enabled: spreadView.closingIndex >= 0
825 UbuntuNumberAnimation {}
829 onSideStageEnabledChanged: refreshStage()
832 property bool _constructing: true;
834 if (!_constructing) {
835 priv.updateMainAndSideStageIndexes();
839 Component.onCompleted: {
840 // a top level window is always the focused one when it first appears, unfocusing
841 // any preexisting one
844 _constructing = false;
846 Component.onDestruction: {
847 WindowStateStorage.saveStage(application.appId, stage);
850 function refreshStage() {
851 var newStage = ApplicationInfoInterface.MainStage;
852 if (priv.sideStageEnabled) {
853 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
854 newStage = WindowStateStorage.getStage(application.appId);
862 var tileProgress = (spreadView.shiftedContentX - behavioredZIndex * spreadView.tileDistance) / spreadView.width;
863 // Some tiles (nextInStack, active) need to move directly from the beginning, normalize progress to immediately start at 0
864 if ((index == spreadView.nextInStack && spreadView.phase < 2) || (active && spreadView.phase < 1)) {
865 tileProgress += behavioredZIndex * spreadView.tileDistance / spreadView.width;
870 // TODO: Hiding tile when progress is such that it will be off screen.
871 property bool occluded: {
872 if (spreadView.active && !offScreen) return false;
873 else if (spreadTile.active) return false;
874 else if (xTranslateAnimating) return false;
875 else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
879 visible: Powerd.status == Powerd.On &&
880 !greeter.fullyShown &&
884 if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
885 if (progress < spreadView.positionMarker1) {
887 } else if (progress < spreadView.positionMarker1 + snappingCurve.period) {
888 return spreadView.positionMarker1 + snappingCurve.value * 3;
890 return spreadView.positionMarker2;
896 shellOrientationAngle: root.shellOrientationAngle
897 shellOrientation: root.shellOrientation
898 orientations: root.orientations
903 when: spreadTile.stage == ApplicationInfoInterface.MainStage
907 when: spreadTile.stage == ApplicationInfoInterface.SideStage
911 width: spreadView.sideStageWidth
912 height: priv.landscapeHeight
914 supportedOrientations: Qt.PortraitOrientation
915 shellOrientationAngle: 0
916 shellOrientation: Qt.PortraitOrientation
917 orientations: sideStageOrientations
923 id: sideStageOrientations
924 primary: Qt.PortraitOrientation
925 native_: Qt.PortraitOrientation
926 portrait: root.orientations.portrait
927 invertedPortrait: root.orientations.invertedPortrait
928 landscape: root.orientations.landscape
929 invertedLandscape: root.orientations.invertedLandscape
935 SequentialAnimation {
938 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
942 // rotate immediately.
943 spreadTile.matchShellOrientation();
944 if (priv.focusedAppDelegate === spreadTile &&
945 priv.sideStageEnabled && !sideStage.shown) {
946 // Sidestage was focused, so show the side stage.
955 SequentialAnimation {
958 if (priv.sideStageDelegate === spreadTile &&
959 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
960 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
961 spreadTile.focus = true;
967 properties: "width,height,supportedOrientations,shellOrientationAngle,shellOrientation,orientations"
969 ScriptAction { script: { spreadTile.matchShellOrientation(); } }
975 if (spreadView.phase == 2) {
976 spreadView.snapTo(index);
982 spreadView.draggedDelegateCount++;
984 spreadView.draggedDelegateCount--;
989 spreadView.closingIndex = index;
990 if (spreadTile.surface) {
991 spreadTile.surface.close();
992 } else if (spreadTile.application) {
993 root.applicationManager.stopApplication(spreadTile.application.appId);
995 // should never happen
996 console.warn("Can't close topLevelSurfaceList entry as it has neither"
997 + " a surface nor an application");
1003 when: model.id == priv.mainStageItemId
1004 property: "mainAppWindowOrientationAngle"
1005 value: appWindowOrientationAngle
1009 when: model.id == priv.mainStageItemId
1010 property: "mainAppOrientationChangesEnabled"
1011 value: orientationChangesEnabled
1016 type: EasingCurve.Linear
1017 period: (spreadView.positionMarker2 - spreadView.positionMarker1) / 3
1018 progress: spreadTile.progress - spreadView.positionMarker1
1021 StagedFullscreenPolicy {
1022 id: fullscreenPolicy
1023 surface: model.surface
1027 onStageAboutToBeUnloaded: fullscreenPolicy.active = false
1034 TabletSideStageTouchGesture {
1036 anchors.fill: parent
1037 enabled: priv.sideStageEnabled && !spreadView.active
1038 property var dragObject: null
1040 property Item spreadDelegate
1042 dragComponent: dragComponent
1043 dragComponentProperties: { "spreadDelegate": spreadDelegate }
1046 function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
1048 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
1049 if (!delegateAtCenter) return;
1051 spreadDelegate = delegateAtCenter;
1055 if (sideStage.shown) {
1063 // If we're dragging to the sidestage.
1064 if (!sideStage.shown) {
1072 property Item spreadDelegate
1074 surface: spreadDelegate ? spreadDelegate.surface : null
1076 consumesInput: false
1078 resizeSurface: false
1082 height: units.gu(40)
1084 Drag.hotSpot.x: width/2
1085 Drag.hotSpot.y: height/2
1086 // only accept opposite stage.
1088 if (!surface) return "Disabled";
1090 if (spreadDelegate.stage === ApplicationInfo.MainStage) {
1091 if (spreadDelegate.application.supportedOrientations
1092 & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1103 //eat touch events during the right edge gesture
1105 anchors.fill: parent
1106 enabled: spreadDragArea.dragging
1111 objectName: "spreadDragArea"
1112 x: parent.width - root.dragAreaWidth
1113 anchors { top: parent.top; bottom: parent.bottom }
1114 width: root.dragAreaWidth
1115 direction: Direction.Leftwards
1116 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
1118 property var gesturePoints: new Array()
1120 onTouchPositionChanged: {
1122 spreadView.phase = 0;
1123 spreadView.contentX = -spreadView.shift;
1127 var dragX = -touchPosition.x + spreadDragArea.width - spreadView.shift;
1128 var maxDrag = spreadView.width * spreadView.positionMarker4 - spreadView.shift;
1129 spreadView.contentX = Math.min(dragX, maxDrag);
1131 gesturePoints.push(touchPosition.x);
1134 onDraggingChanged: {
1136 // Gesture recognized. Start recording this gesture
1139 // Ok. The user released. Find out if it was a one-way movement.
1140 var oneWayFlick = priv.evaluateOneWayFlick(gesturePoints);
1143 if (oneWayFlick && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
1144 // If it was a short one-way movement, do the Alt+Tab switch
1145 // no matter if we didn't cross positionMarker1 yet.
1146 spreadView.snapTo(spreadView.nextInStack);
1148 if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker1) {
1150 } else if (spreadView.shiftedContentX < spreadView.width * spreadView.positionMarker2) {
1151 spreadView.snapTo(spreadView.nextInStack);
1153 // otherwise snap to the closest snap position we can find
1154 // (might be back to start, to app 1 or to spread)
1165 // NB: it does its own positioning according to the specified edge
1169 spreadView.snapToSpread();
1171 material: Component {
1174 width: parent.height
1175 height: parent.width
1177 anchors.centerIn: parent
1178 gradient: Gradient {
1179 GradientStop { position: 0.0; color: Qt.rgba(0.16,0.16,0.16,0.7)}
1180 GradientStop { position: 1.0; color: Qt.rgba(0.16,0.16,0.16,0)}