@@ -27,19 +27,24 @@ AbstractStage {
27 objectName: "stages"
27 objectName: "stages"
28 anchors.fill: parent
28 anchors.fill: parent
30+ // <tutorial-hacks> The Tutorial looks into our implementation details
30 property alias sideStageVisible: spreadView.sideStageVisible
31 property alias sideStageVisible: spreadView.sideStageVisible
31 property alias sideStageWidth: spreadView.sideStageWidth
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
33 // Functions to be called from outside
37 // Functions to be called from outside
34 function updateFocusedAppOrientation() {
38 function updateFocusedAppOrientation() {
35− var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
39+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
36− if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
40+
37− spreadRepeater.itemAt(mainStageAppIndex).matchShellOrientation();
41+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
42+ spreadRepeater.itemAt(mainStageIndex).matchShellOrientation();
40 for (var i = 0; i < spreadRepeater.count; ++i) {
45 for (var i = 0; i < spreadRepeater.count; ++i) {
42− if (i === mainStageAppIndex) {
47+ if (i === mainStageIndex) {
@@ -60,16 +65,14 @@ AbstractStage {
62 function updateFocusedAppOrientationAnimated() {
67 function updateFocusedAppOrientationAnimated() {
63− var mainStageAppIndex = priv.indexOf(priv.mainStageAppId);
68+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
64− if (mainStageAppIndex >= 0 && mainStageAppIndex < spreadRepeater.count) {
69+ if (priv.mainStageItemId && mainStageIndex >= 0 && mainStageIndex < spreadRepeater.count) {
65− spreadRepeater.itemAt(mainStageAppIndex).animateToShellOrientation();
70+ spreadRepeater.itemAt(mainStageIndex).animateToShellOrientation();
68− if (priv.sideStageAppId) {
73+ var sideStageIndex = root.topLevelSurfaceList.indexForId(priv.sideStageItemId);
69− var sideStageAppIndex = priv.indexOf(priv.sideStageAppId);
74+ if (sideStageIndex >= 0 && sideStageIndex < spreadRepeater.count) {
70− if (sideStageAppIndex >= 0 && sideStageAppIndex < spreadRepeater.count) {
75+ spreadRepeater.itemAt(sideStageIndex).matchShellOrientation();
71− spreadRepeater.itemAt(sideStageAppIndex).matchShellOrientation();
@@ -79,15 +82,22 @@ AbstractStage {
82− mainAppWindow: priv.focusedAppDelegate ? priv.focusedAppDelegate.appWindow : null
84 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
85 orientationChangesEnabled: priv.mainAppOrientationChangesEnabled
88+ if (priv.mainStageItemId > 0) {
89+ var index = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
90+ return root.topLevelSurfaceList.applicationAt(index);
86 supportedOrientations: {
96 supportedOrientations: {
87 if (mainApp) {
97 if (mainApp) {
88 var orientations = mainApp.supportedOrientations;
98 var orientations = mainApp.supportedOrientations;
89 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
99 orientations |= Qt.LandscapeOrientation | Qt.InvertedLandscapeOrientation;
90− if (priv.sideStageAppId && !spreadView.surfaceDragging) {
100+ if (priv.sideStageItemId && !spreadView.surfaceDragging) {
91 // If we have a sidestage app, support Portrait orientation
101 // If we have a sidestage app, support Portrait orientation
92 // so that it will switch the sidestage app to mainstage on rotate
102 // so that it will switch the sidestage app to mainstage on rotate
93 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
103 orientations |= Qt.PortraitOrientation|Qt.InvertedPortraitOrientation;
@@ -118,7 +128,7 @@ AbstractStage {
118 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
128 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
119 // left edge drag released. Minimum distance is given by design.
129 // left edge drag released. Minimum distance is given by design.
120 if (priv.oldInverseProgress > units.gu(22)) {
130 if (priv.oldInverseProgress > units.gu(22)) {
121− ApplicationManager.requestFocusApplication("unity8-dash");
131+ root.applicationManager.requestFocusApplication("unity8-dash");
124 priv.oldInverseProgress = inverseProgress;
134 priv.oldInverseProgress = inverseProgress;
@@ -156,17 +166,51 @@ AbstractStage {
170+ target: root.topLevelSurfaceList
171+ onListChanged: priv.updateMainAndSideStageIndexes()
159 QtObject {
174 QtObject {
161 objectName: "stagesPriv"
176 objectName: "stagesPriv"
163− property string focusedAppId: ApplicationManager.focusedApplicationId
178+ function updateMainAndSideStageIndexes() {
164− readonly property var focusedAppDelegate: {
179+ var choseMainStage = false;
165− var index = indexOf(focusedAppId);
180+ var choseSideStage = false;
166− return index >= 0 && index < spreadRepeater.count ? spreadRepeater.itemAt(index) : null
181+
182+ if (!root.topLevelSurfaceList)
185+ for (var i = 0; i < spreadRepeater.count && (!choseMainStage || !choseSideStage); ++i) {
186+ var spreadDelegate = spreadRepeater.itemAt(i);
187+ if (sideStage.shown && spreadDelegate.stage == ApplicationInfoInterface.SideStage
188+ && !choseSideStage) {
189+ priv.sideStageDelegate = spreadDelegate
190+ priv.sideStageItemId = root.topLevelSurfaceList.idAt(i);
191+ priv.sideStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
192+ choseSideStage = true;
193+ } else if (!choseMainStage && spreadDelegate.stage == ApplicationInfoInterface.MainStage) {
194+ priv.mainStageDelegate = spreadDelegate;
195+ priv.mainStageItemId = root.topLevelSurfaceList.idAt(i);
196+ priv.mainStageAppId = root.topLevelSurfaceList.applicationAt(i).appId;
197+ choseMainStage = true;
200+ if (!choseMainStage) {
201+ priv.mainStageDelegate = null;
202+ priv.mainStageItemId = 0;
203+ priv.mainStageAppId = "";
205+ if (!choseSideStage) {
206+ priv.sideStageDelegate = null;
207+ priv.sideStageItemId = 0;
208+ priv.sideStageAppId = "";
169− property string oldFocusedAppId: ""
212+ property var focusedAppDelegate: null
170 property bool mainAppOrientationChangesEnabled: false
214 property bool mainAppOrientationChangesEnabled: false
172 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
216 property real landscapeHeight: root.orientations.native_ == Qt.LandscapeOrientation ?
@@ -175,36 +219,21 @@ AbstractStage {
175 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
219 property bool shellIsLandscape: root.shellOrientation === Qt.LandscapeOrientation
176 || root.shellOrientation === Qt.InvertedLandscapeOrientation
220 || root.shellOrientation === Qt.InvertedLandscapeOrientation
178− property string mainStageAppId
222+ property var mainStageDelegate: null
179− property string sideStageAppId
223+ property var sideStageDelegate: null
225+ property int mainStageItemId: 0
226+ property int sideStageItemId: 0
181− // For convenience, keep properties of the first two apps in the model
228+ property string mainStageAppId: ""
182− property string appId0
229+ property string sideStageAppId: ""
183− property string appId1
185 property int oldInverseProgress: 0
231 property int oldInverseProgress: 0
187 property int highlightIndex: 0
233 property int highlightIndex: 0
189− onFocusedAppIdChanged: updateStageApps()
191− onFocusedAppDelegateChanged: {
192− if (focusedAppDelegate) {
193− focusedAppDelegate.focus = true;
197 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
235 property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
198 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
236 (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
199− function indexOf(appId) {
200− for (var i = 0; i < ApplicationManager.count; i++) {
201− if (ApplicationManager.get(i).appId == appId) {
208 function evaluateOneWayFlick(gesturePoints) {
237 function evaluateOneWayFlick(gesturePoints) {
209 // Need to have at least 3 points to recognize it as a flick
238 // Need to have at least 3 points to recognize it as a flick
210 if (gesturePoints.length < 3) {
239 if (gesturePoints.length < 3) {
@@ -233,88 +262,45 @@ AbstractStage {
233 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
262 spreadView.contentX = highlightIndex * spreadView.contentWidth / (spreadRepeater.count + 2)
236− function getTopApp(stage) {
237− for (var i = 0; i < ApplicationManager.count; i++) {
238− var app = ApplicationManager.get(i)
239− if (app.stage === stage) {
246− function setAppStage(appId, stage, save) {
247− var app = ApplicationManager.findApplication(appId);
251− WindowStateStorage.saveStage(appId, stage);
256− function updateStageApps() {
257− var app = priv.getTopApp(ApplicationInfoInterface.MainStage);
258− priv.mainStageAppId = app ? app.appId : ""
261− if (sideStage.shown) {
262− app = priv.getTopApp(ApplicationInfoInterface.SideStage);
263− priv.sideStageAppId = app ? app.appId : ""
265− priv.sideStageAppId = "";
268− appId0 = ApplicationManager.count >= 1 ? ApplicationManager.get(0).appId : "";
269− appId1 = ApplicationManager.count > 1 ? ApplicationManager.get(1).appId : "";
272 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
265 readonly property bool sideStageEnabled: root.shellOrientation == Qt.LandscapeOrientation ||
273 root.shellOrientation == Qt.InvertedLandscapeOrientation
266 root.shellOrientation == Qt.InvertedLandscapeOrientation
274− Component.onCompleted: updateStageApps();
277− Connections {
269+ Instantiator {
278− target: ApplicationManager
270+ model: root.applicationManager
279− onFocusRequested: {
271+ delegate: QtObject {
280− if (spreadView.interactive) {
272+ property var stateBinding: Binding {
281− spreadView.snapTo(priv.indexOf(appId));
273+ readonly property bool isDash: model.application ? model.application.appId == "unity8-dash" : false
282− } else {
274+ target: model.application
283− ApplicationManager.focusApplication(appId);
275+ property: "requestedState"
277+ // NB: the first application clause is just to ensure we never get warnings for trying to access
278+ // members of a null variable.
279+ value: model.application &&
281+ (isDash && root.keepDashRunning)
282+ || (!root.suspended && (model.application.appId === priv.mainStageAppId
283+ || model.application.appId === priv.sideStageAppId))
285+ ? ApplicationInfoInterface.RequestedRunning
286+ : ApplicationInfoInterface.RequestedSuspended
287− onApplicationAdded: {
289+ property var lifecycleBinding: Binding {
288− if (spreadView.phase == 2) {
290+ target: model.application
289− spreadView.snapTo(ApplicationManager.count - 1);
291+ property: "exemptFromLifecycle"
290− } else {
292+ value: model.application
291− spreadView.phase = 0;
293+ ? (!model.application.isTouchApp || isExemptFromLifecycle(model.application.appId))
292− spreadView.contentX = -spreadView.shift;
294+ : false
293− ApplicationManager.focusApplication(appId);
297− onApplicationRemoved: {
299+ Binding {
298− if (priv.mainStageAppId == appId) {
300+ target: MirFocusController
299− ApplicationManager.focusApplication("unity8-dash")
301+ property: "focusedSurface"
300− }
302+ value: priv.focusedAppDelegate ? priv.focusedAppDelegate.surface : null
301− if (priv.sideStageAppId == appId) {
303+ when: root.parent && !spreadRepeater.startingUp
302− var app = priv.getTopApp(ApplicationInfoInterface.SideStage);
303− priv.sideStageAppId = app === null ? "" : app.appId;
306− if (ApplicationManager.count == 0) {
307− spreadView.phase = 0;
308− spreadView.contentX = -spreadView.shift;
309− } else if (spreadView.closingIndex == -1) {
310− // Unless we're closing the app ourselves in the spread,
311− // lets make sure the spread doesn't mess up by the changing app list.
312− spreadView.phase = 0;
313− spreadView.contentX = -spreadView.shift;
315− ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
320 Flickable {
306 Flickable {
@@ -365,16 +351,45 @@ AbstractStage {
365 property int selectedIndex: -1
351 property int selectedIndex: -1
366 property int draggedDelegateCount: 0
352 property int draggedDelegateCount: 0
367 property int closingIndex: -1
353 property int closingIndex: -1
368− property var selectedApplication: selectedIndex !== -1 ? ApplicationManager.get(selectedIndex) : null
354+ property var selectedDelegate: selectedIndex !== -1 ? spreadRepeater.itemAt(selectedIndex) : null
370− // FIXME: Workaround Flickable's not keepping its contentX still when resized
356+ // <FIXME-contentX> Workaround Flickable's behavior of bringing contentX back between valid boundaries
371− onContentXChanged: { forceItToRemainStillIfBeingResized(); }
357+ // when resized. The proper way to fix this is refactoring PhoneStage so that it doesn't
358+ // rely on having Flickable.contentX keeping an out-of-bounds value when it's set programatically
359+ // (as opposed to having contentX reaching an out-of-bounds value through dragging, which will trigger
360+ // the Flickable.boundsBehavior upon release).
361+ onContentXChanged: {
362+ if (!undoContentXReset()) {
363+ forceItToRemainStillIfBeingResized();
372 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
366 onShiftChanged: { forceItToRemainStillIfBeingResized(); }
373 function forceItToRemainStillIfBeingResized() {
367 function forceItToRemainStillIfBeingResized() {
374− if (root.beingResized && contentX != -shift) {
368+ if (root.beingResized && contentX != -spreadView.shift) {
369+ contentX = -spreadView.shift;
372+ function undoContentXReset() {
373+ if (contentWidth <= 0) {
374+ contentWidthOnLastContentXChange = contentWidth;
375+ lastContentX = contentX;
379+ if (contentWidth != contentWidthOnLastContentXChange
380+ && lastContentX == -shift && contentX == 0) {
381+ // Flickable is resetting contentX because contentWidth has changed. Undo it.
375 contentX = -shift;
382 contentX = -shift;
386+ contentWidthOnLastContentXChange = contentWidth;
387+ lastContentX = contentX;
390+ property real contentWidthOnLastContentXChange: -1
391+ property real lastContentX: 0
392+ // </FIXME-contentX>
379 property bool animateX: true
394 property bool animateX: true
380 property bool beingResized: root.beingResized
395 property bool beingResized: root.beingResized
@@ -387,31 +402,33 @@ AbstractStage {
390− property real sideStageDragProgress: sideStage.progress
391− property bool sideStageVisible: priv.sideStageAppId
392 property real sideStageWidth: units.gu(40)
405 property real sideStageWidth: units.gu(40)
394 property bool surfaceDragging: triGestureArea.recognisedDrag
407 property bool surfaceDragging: triGestureArea.recognisedDrag
396− // In case the ApplicationManager already holds an app when starting up we're missing animations
409+ readonly property bool sideStageVisible: priv.sideStageItemId != 0
411+ // In case applicationManager already holds an app when starting up we're missing animations
397 // Make sure we end up in the same state
412 // Make sure we end up in the same state
398 Component.onCompleted: {
413 Component.onCompleted: {
399 spreadView.contentX = -spreadView.shift
414 spreadView.contentX = -spreadView.shift
402 property int nextInStack: {
417 property int nextInStack: {
418+ var mainStageIndex = priv.mainStageDelegate ? priv.mainStageDelegate.index : -1;
419+ var sideStageIndex = priv.sideStageDelegate ? priv.sideStageDelegate.index : -1;
403 switch (state) {
420 switch (state) {
404 case "main":
421 case "main":
405− if (ApplicationManager.count > 1) {
422+ if (root.topLevelSurfaceList.count > 1) {
406 return 1;
423 return 1;
408 return -1;
425 return -1;
409 case "mainAndOverlay":
426 case "mainAndOverlay":
410− if (ApplicationManager.count <= 2) {
427+ if (root.topLevelSurfaceList.count <= 2) {
411 return -1;
428 return -1;
413− if (priv.appId0 == priv.mainStageAppId || priv.appId0 == priv.sideStageAppId) {
430+ if (mainStageIndex == 0 || sideStageIndex == 0) {
414− if (priv.appId1 == priv.mainStageAppId || priv.appId1 == priv.sideStageAppId) {
431+ if (mainStageIndex == 1 || sideStageIndex == 1) {
415 return 2;
432 return 2;
417 return 1;
434 return 1;
@@ -422,7 +439,7 @@ AbstractStage {
423 return -1;
440 return -1;
425− property int nextZInStack: indexToZIndex(nextInStack)
442+ property int nextZInStack
427 states: [
444 states: [
@@ -442,13 +459,13 @@ AbstractStage {
445− if ((priv.mainStageAppId && !priv.sideStageAppId) || !priv.sideStageEnabled) {
462+ if ((priv.mainStageItemId && !priv.sideStageItemId) || !priv.sideStageEnabled) {
446 return "main";
463 return "main";
448− if (!priv.mainStageAppId && priv.sideStageAppId) {
465+ if (!priv.mainStageItemId && priv.sideStageItemId) {
449 return "overlay";
466 return "overlay";
451− if (priv.mainStageAppId && priv.sideStageAppId) {
468+ if (priv.mainStageItemId && priv.sideStageItemId) {
452 return "mainAndOverlay";
469 return "mainAndOverlay";
454 return "empty";
471 return "empty";
@@ -513,38 +530,41 @@ AbstractStage {
513 // only shuffle when we've got a main and overlay
530 // only shuffle when we've got a main and overlay
514 if (state !== "mainAndOverlay") return index;
531 if (state !== "mainAndOverlay") return index;
516− var app = ApplicationManager.get(index);
533+ var app = root.topLevelSurfaceList.applicationAt(index);
517 if (!app) {
534 if (!app) {
518 return index;
535 return index;
537+ var stage = spreadRepeater.itemAt(index) ? spreadRepeater.itemAt(index).stage : app.stage;
521 // don't shuffle indexes greater than "actives or next"
539 // don't shuffle indexes greater than "actives or next"
522 if (index > 2) return index;
540 if (index > 2) return index;
524− if (app.appId === priv.mainStageAppId) {
542+ var mainStageIndex = root.topLevelSurfaceList.indexForId(priv.mainStageItemId);
544+ if (index == mainStageIndex) {
525 // Active main stage always at 0
545 // Active main stage always at 0
526 return 0;
546 return 0;
529 if (spreadView.nextInStack > 0) {
549 if (spreadView.nextInStack > 0) {
530− var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
550+ var stageOfNextInStack = spreadRepeater.itemAt(spreadView.nextInStack).stage;
532 if (index === spreadView.nextInStack) {
552 if (index === spreadView.nextInStack) {
533 // this is the next app in stack.
553 // this is the next app in stack.
535− if (app.stage === ApplicationInfoInterface.SideStage) {
555+ if (stage === ApplicationInfoInterface.SideStage) {
536 // if the next app in stack is a sidestage app, it must order on top of other side stage app
556 // if the next app in stack is a sidestage app, it must order on top of other side stage app
537− return Math.min(2, ApplicationManager.count-1);
557+ return Math.min(2, root.topLevelSurfaceList.count-1);
539 return 1;
559 return 1;
541− if (nextAppInStack.stage === ApplicationInfoInterface.SideStage) {
561+ if (stageOfNextInStack === ApplicationInfoInterface.SideStage) {
542 // if the next app in stack is a sidestage app, it must order on top of other side stage app
562 // if the next app in stack is a sidestage app, it must order on top of other side stage app
543 return 1;
563 return 1;
545− return Math.min(2, ApplicationManager.count-1);
565+ return Math.min(2, root.topLevelSurfaceList.count-1);
547− return Math.min(index+1, ApplicationManager.count-1);
567+ return Math.min(index+1, root.topLevelSurfaceList.count-1);
550 SequentialAnimation {
570 SequentialAnimation {
@@ -562,12 +582,13 @@ AbstractStage {
562 script: {
582 script: {
563 if (spreadView.selectedIndex >= 0) {
583 if (spreadView.selectedIndex >= 0) {
564 var newIndex = spreadView.selectedIndex;
584 var newIndex = spreadView.selectedIndex;
565− var application = ApplicationManager.get(newIndex);
585+ var application = root.topLevelSurfaceList.applicationAt(newIndex);
566− if (application.stage === ApplicationInfoInterface.SideStage) {
586+ var spreadDelegate = spreadRepeater.itemAt(newIndex);
587+ if (spreadDelegate.stage === ApplicationInfoInterface.SideStage) {
567 sideStage.showNow();
588 sideStage.showNow();
569 spreadView.selectedIndex = -1;
590 spreadView.selectedIndex = -1;
570− ApplicationManager.focusApplication(application.appId);
591+ spreadDelegate.focus = true;
571 spreadView.phase = 0;
592 spreadView.phase = 0;
572 spreadView.contentX = -spreadView.shift;
593 spreadView.contentX = -spreadView.shift;
@@ -583,7 +604,7 @@ AbstractStage {
583 MouseArea {
604 MouseArea {
584 id: spreadRow
605 id: spreadRow
585 x: spreadView.contentX
606 x: spreadView.contentX
586− width: spreadView.width + Math.max(spreadView.width, ApplicationManager.count * spreadView.tileDistance)
607+ width: spreadView.width + Math.max(spreadView.width, root.topLevelSurfaceList.count * spreadView.tileDistance)
587 height: root.height
608 height: root.height
589 onClicked: {
610 onClicked: {
@@ -601,8 +622,8 @@ AbstractStage {
601 enabled: priv.sideStageEnabled
622 enabled: priv.sideStageEnabled
603 onDropped: {
624 onDropped: {
604− priv.setAppStage(drag.source.appId, ApplicationInfoInterface.MainStage, true);
625+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.MainStage;
605− ApplicationManager.focusApplication(drag.source.appId);
626+ drop.source.spreadDelegate.focus = true;
607 keys: "SideStage"
628 keys: "SideStage"
@@ -613,12 +634,12 @@ AbstractStage {
613 height: priv.landscapeHeight
634 height: priv.landscapeHeight
614 x: spreadView.width - width
635 x: spreadView.width - width
616− if (!priv.mainStageAppId) return 0;
637+ if (!priv.mainStageItemId) return 0;
618− if (priv.sideStageAppId && spreadView.nextInStack > 0) {
639+ if (priv.sideStageItemId && spreadView.nextInStack > 0) {
619− var nextAppInStack = ApplicationManager.get(spreadView.nextInStack);
640+ var nextDelegateInStack = spreadRepeater.itemAt(spreadView.nextInStack);
621− if (nextAppInStack.stage === ApplicationInfoInterface.MainStage) {
642+ if (nextDelegateInStack.stage === ApplicationInfoInterface.MainStage) {
622 // if the next app in stack is a main stage app, put the sidestage on top of it.
643 // if the next app in stack is a main stage app, put the sidestage on top of it.
623 return 2;
644 return 2;
@@ -633,12 +654,11 @@ AbstractStage {
633 Behavior on opacity { UbuntuNumberAnimation {} }
654 Behavior on opacity { UbuntuNumberAnimation {} }
635 onShownChanged: {
656 onShownChanged: {
636− if (!shown && ApplicationManager.focusedApplicationId == priv.sideStageAppId) {
657+ if (!shown && priv.sideStageDelegate && priv.focusedAppDelegate === priv.sideStageDelegate
637− ApplicationManager.requestFocusApplication(priv.mainStageAppId);
658+ && priv.mainStageDelegate) {
638− }
659+ priv.mainStageDelegate.focus = true;
639− priv.updateStageApps();
660+ } else if (shown && priv.sideStageDelegate) {
640− if (shown && priv.sideStageAppId) {
661+ priv.sideStageDelegate.focus = true;
641− ApplicationManager.requestFocusApplication(priv.sideStageAppId);
@@ -657,8 +677,8 @@ AbstractStage {
658 onDropped: {
678 onDropped: {
659 if (drop.keys == "MainStage") {
679 if (drop.keys == "MainStage") {
660− priv.setAppStage(drop.source.appId, ApplicationInfoInterface.SideStage, true);
680+ drop.source.spreadDelegate.stage = ApplicationInfoInterface.SideStage;
661− ApplicationManager.requestFocusApplication(drop.source.appId);
681+ drop.source.spreadDelegate.focus = true;
@@ -671,41 +691,102 @@ AbstractStage {
674− Repeater {
694+ TopLevelSurfaceRepeater {
675 id: spreadRepeater
695 id: spreadRepeater
676 objectName: "spreadRepeater"
696 objectName: "spreadRepeater"
677− model: ApplicationManager
697+ model: root.topLevelSurfaceList
700+ priv.updateMainAndSideStageIndexes();
701+ if (spreadView.phase == 2) {
702+ spreadView.snapTo(index);
707+ priv.updateMainAndSideStageIndexes();
708+ // Unless we're closing the app ourselves in the spread,
709+ // lets make sure the spread doesn't mess up by the changing app list.
710+ if (spreadView.closingIndex == -1) {
711+ spreadView.phase = 0;
712+ spreadView.contentX = -spreadView.shift;
716+ function focusTopMostApp() {
717+ if (spreadRepeater.count > 0) {
718+ var topmostDelegate = spreadRepeater.itemAt(0);
719+ topmostDelegate.focus = true;
679 delegate: TransformedTabletSpreadDelegate {
723 delegate: TransformedTabletSpreadDelegate {
680 id: spreadTile
724 id: spreadTile
681− objectName: model.appId ? "tabletSpreadDelegate_" + model.appId
725+ objectName: "spreadDelegate_" + model.id
682− : "tabletSpreadDelegate_null";
726+
727+ readonly property int index: model.index
683 width: spreadView.width
728 width: spreadView.width
684 height: spreadView.height
729 height: spreadView.height
685− active: appId == priv.mainStageAppId || appId == priv.sideStageAppId
730+ active: model.id == priv.mainStageItemId || model.id == priv.sideStageItemId
686 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
731 zIndex: selected && stage == ApplicationInfoInterface.MainStage ? 0 : spreadView.indexToZIndex(index)
733+ if (spreadView.nextInStack == model.index) {
734+ spreadView.nextZInStack = zIndex;
687 selected: spreadView.selectedIndex == index
737 selected: spreadView.selectedIndex == index
688 otherSelected: spreadView.selectedIndex >= 0 && !selected
738 otherSelected: spreadView.selectedIndex >= 0 && !selected
689− isInSideStage: priv.sideStageAppId === appId
739+ isInSideStage: priv.sideStageItemId == model.id
690 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
740 interactive: !spreadView.interactive && spreadView.phase === 0 && root.interactive
691 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
741 swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
692 maximizedAppTopMargin: root.maximizedAppTopMargin
742 maximizedAppTopMargin: root.maximizedAppTopMargin
693− dragOffset: !isDash && appId == priv.mainStageAppId && root.inverseProgress > 0 && spreadView.phase === 0 ? root.inverseProgress : 0
743+ dragOffset: !isDash && model.id == priv.mainStageItemId && root.inverseProgress > 0
694− application: ApplicationManager.get(index)
744+ && spreadView.phase === 0 ? root.inverseProgress : 0
745+ application: model.application
746+ surface: model.surface
695 closeable: !isDash
747 closeable: !isDash
696 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
748 highlightShown: root.altTabPressed && priv.highlightIndex == zIndex
698− readonly property bool wantsMainStage: model.stage == ApplicationInfoInterface.MainStage
750+ readonly property bool wantsMainStage: stage == ApplicationInfoInterface.MainStage
700− readonly property string appId: model.appId
752+ readonly property bool isDash: model.application.appId == "unity8-dash"
701− readonly property bool isDash: model.appId == "unity8-dash"
753+
755+ if (focus && !spreadRepeater.startingUp) {
756+ priv.focusedAppDelegate = spreadTile;
757+ root.topLevelSurfaceList.raiseId(model.id);
759+ if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
764+ target: model.surface
765+ onFocusRequested: spreadTile.focus = true;
768+ target: model.application
770+ if (!model.surface) {
771+ // when an app has no surfaces, we assume there's only one entry representing it:
773+ spreadTile.focus = true;
775+ // if the application has surfaces, focus request should be at surface-level.
704 fullscreen: {
780 fullscreen: {
705− if (mainApp && stage === ApplicationInfoInterface.SideStage) {
781+ if (priv.mainStageDelegate && stage === ApplicationInfoInterface.SideStage) {
706− return mainApp.fullscreen;
782+ return priv.mainStageDelegate.fullscreen;
783+ } else if (surface) {
784+ return surface.state === Mir.FullscreenState;
785+ } else if (application) {
786+ return application.fullscreen;
708− return application ? application.fullscreen : false;
711 supportedOrientations: {
792 supportedOrientations: {
@@ -726,23 +807,6 @@ AbstractStage {
731− target: spreadTile.application
732− property: "exemptFromLifecycle"
733− value: !model.isTouchApp || isExemptFromLifecycle(model.appId)
737− target: spreadTile.application
738− property: "requestedState"
739− value: (isDash && root.keepDashRunning)
740− || (!root.suspended && (model.appId == priv.mainStageAppId
741− || model.appId == priv.sideStageAppId))
742− ? ApplicationInfoInterface.RequestedRunning
743− : ApplicationInfoInterface.RequestedSuspended
746 // FIXME: A regular binding doesn't update any more after closing an app.
810 // FIXME: A regular binding doesn't update any more after closing an app.
747 // Using a Binding for now.
811 // Using a Binding for now.
748 Binding {
812 Binding {
@@ -762,34 +826,33 @@ AbstractStage {
762 onSideStageEnabledChanged: refreshStage()
826 onSideStageEnabledChanged: refreshStage()
829+ property bool _constructing: true;
831+ if (!_constructing) {
832+ priv.updateMainAndSideStageIndexes();
765 Component.onCompleted: {
836 Component.onCompleted: {
766− refreshStage()
837+ // a top level window is always the focused one when it first appears, unfocusing
767− stageChanged.connect(priv.updateStageApps);
838+ // any preexisting one
841+ _constructing = false;
843+ Component.onDestruction: {
844+ WindowStateStorage.saveStage(model.application.appId, stage);
770 function refreshStage() {
847 function refreshStage() {
771− var stage = ApplicationInfoInterface.MainStage;
848+ var newStage = ApplicationInfoInterface.MainStage;
772 if (priv.sideStageEnabled) {
849 if (priv.sideStageEnabled) {
773 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
850 if (application && application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
774− stage = WindowStateStorage.getStage(appId);
851+ newStage = WindowStateStorage.getStage(application.appId);
778− if (model.stage !== stage) {
855+ stage = newStage;
779− priv.setAppStage(appId, stage, false);
783− // This is required because none of the bindings are triggered in some cases:
784− // When an app is closed, it might happen that ApplicationManager.get(nextInStack)
785− // returns a different app even though the nextInStackIndex and all the related
786− // bindings (index, mainStageApp, sideStageApp, etc) don't change. Let's force a
787− // binding update in that case.
789− target: ApplicationManager
790− onApplicationRemoved: spreadTile.z = Qt.binding(function() {
791− return spreadView.indexToZIndex(index);
795 progress: {
858 progress: {
@@ -875,13 +938,10 @@ AbstractStage {
875 script: {
938 script: {
876 // rotate immediately.
939 // rotate immediately.
877 spreadTile.matchShellOrientation();
940 spreadTile.matchShellOrientation();
878− if (ApplicationManager.focusedApplicationId === spreadTile.appId &&
941+ if (priv.focusedAppDelegate === spreadTile &&
879 priv.sideStageEnabled && !sideStage.shown) {
942 priv.sideStageEnabled && !sideStage.shown) {
880 // Sidestage was focused, so show the side stage.
943 // Sidestage was focused, so show the side stage.
881 sideStage.show();
944 sideStage.show();
882− // if we've switched to a main app which doesnt support portrait, hide the side stage.
883− } else if (mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
884− sideStage.hideNow();
@@ -892,10 +952,10 @@ AbstractStage {
892 SequentialAnimation {
952 SequentialAnimation {
893 ScriptAction {
953 ScriptAction {
894 script: {
954 script: {
895− if (priv.sideStageAppId === spreadTile.appId &&
955+ if (priv.sideStageDelegate === spreadTile &&
896 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
956 mainApp && (mainApp.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) == 0) {
897 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
957 // The mainstage app did not natively support portrait orientation, so focus the sidestage.
898− ApplicationManager.requestFocusApplication(spreadTile.appId);
958+ spreadTile.focus = true;
@@ -924,28 +984,26 @@ AbstractStage {
925 onClosed: {
985 onClosed: {
926 spreadView.closingIndex = index;
986 spreadView.closingIndex = index;
927− ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
987+ if (spreadTile.surface) {
928− }
988+ spreadTile.surface.close();
929−
989+ } else if (spreadTile.application) {
930− onFocusChanged: {
990+ root.applicationManager.stopApplication(spreadTile.application.appId);
931− if (focus && ApplicationManager.focusedApplicationId !== appId) {
991+ } else {
932− ApplicationManager.focusApplication(appId);
992+ // should never happen
933− }
993+ console.warn("Can't close topLevelSurfaceList entry as it has neither"
934−
994+ + " a surface nor an application");
935− if (focus && priv.sideStageEnabled && stage === ApplicationInfoInterface.SideStage) {
940 Binding {
998 Binding {
941 target: root
999 target: root
942− when: model.appId == priv.mainStageAppId
1000+ when: model.id == priv.mainStageItemId
943 property: "mainAppWindowOrientationAngle"
1001 property: "mainAppWindowOrientationAngle"
944 value: appWindowOrientationAngle
1002 value: appWindowOrientationAngle
946 Binding {
1004 Binding {
947 target: priv
1005 target: priv
948− when: model.appId == priv.mainStageAppId
1006+ when: model.id == priv.mainStageItemId
949 property: "mainAppOrientationChangesEnabled"
1007 property: "mainAppOrientationChangesEnabled"
950 value: orientationChangesEnabled
1008 value: orientationChangesEnabled
@@ -959,7 +1017,7 @@ AbstractStage {
960 StagedFullscreenPolicy {
1018 StagedFullscreenPolicy {
961 id: fullscreenPolicy
1019 id: fullscreenPolicy
962− application: spreadTile.application
1020+ surface: model.surface
964 Connections {
1022 Connections {
965 target: root
1023 target: root
@@ -975,17 +1033,19 @@ AbstractStage {
975 anchors.fill: parent
1033 anchors.fill: parent
976 enabled: priv.sideStageEnabled && !spreadView.active
1034 enabled: priv.sideStageEnabled && !spreadView.active
977 property var dragObject: null
1035 property var dragObject: null
978− property string appId: ""
1036+
1037+ property Item spreadDelegate
979 dragComponent: dragComponent
1039 dragComponent: dragComponent
980− dragComponentProperties: { "appId": appId }
1040+ dragComponentProperties: { "spreadDelegate": spreadDelegate }
982 onPressed: {
1042 onPressed: {
983− function matchDelegate(obj) { return String(obj.objectName).indexOf("tabletSpreadDelegate") >= 0; }
1043+ function matchDelegate(obj) { return String(obj.objectName).indexOf("spreadDelegate") >= 0; }
985 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
1045 var delegateAtCenter = Functions.itemAt(spreadRow, x, y, matchDelegate);
986 if (!delegateAtCenter) return;
1046 if (!delegateAtCenter) return;
988− appId = delegateAtCenter.appId;
1048+ spreadDelegate = delegateAtCenter;
991 onClicked: {
1051 onClicked: {
@@ -1005,11 +1065,11 @@ AbstractStage {
1006 Component {
1066 Component {
1007 id: dragComponent
1067 id: dragComponent
1008− SessionContainer {
1068+ SurfaceContainer {
1009− property string appId: ""
1069+ property Item spreadDelegate
1010− property var application: ApplicationManager.findApplication(appId)
1070+
1071+ surface: spreadDelegate ? spreadDelegate.surface : null
1012− session: application ? application.session : null
1013 interactive: false
1073 interactive: false
1014 resizeSurface: false
1074 resizeSurface: false
1015 focus: false
1075 focus: false
@@ -1021,10 +1081,11 @@ AbstractStage {
1021 Drag.hotSpot.y: height/2
1081 Drag.hotSpot.y: height/2
1022 // only accept opposite stage.
1082 // only accept opposite stage.
1023 Drag.keys: {
1083 Drag.keys: {
1024− if (!application) return "Disabled";
1084+ if (!surface) return "Disabled";
1026− if (application.stage === ApplicationInfo.MainStage) {
1086+ if (spreadDelegate.stage === ApplicationInfo.MainStage) {
1027− if (application.supportedOrientations & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1087+ if (spreadDelegate.application.supportedOrientations
1088+ & (Qt.PortraitOrientation|Qt.InvertedPortraitOrientation)) {
1028 return "MainStage";
1089 return "MainStage";
1030 return "Disabled";
1091 return "Disabled";