Merge lp:~unity-team/unity8/lp1475678.surface-occlude into lp:unity8

Proposed by Michał Sawicz on 2015-10-27
Status: Merged
Approved by: Daniel d'Andrada on 2015-10-30
Approved revision: 1989
Merged at revision: 2032
Proposed branch: lp:~unity-team/unity8/lp1475678.surface-occlude
Merge into: lp:unity8
Prerequisite: lp:~mterry/unity8/no-touch-no-lifecycle
Diff against target: 734 lines (+290/-46)
13 files modified
debian/control (+1/-1)
qml/Stages/DesktopStage.qml (+78/-8)
qml/Stages/PhoneStage.qml (+16/-5)
qml/Stages/TabletStage.qml (+17/-1)
qml/Stages/TransformedTabletSpreadDelegate.qml (+2/-0)
tests/mocks/Unity/Application/MirSurface.cpp (+44/-17)
tests/mocks/Unity/Application/MirSurface.h (+14/-4)
tests/mocks/Unity/Application/MirSurfaceItem.cpp (+12/-2)
tests/mocks/Unity/Application/MirSurfaceItem.h (+1/-0)
tests/qmltests/Stages/tst_DesktopStage.qml (+81/-5)
tests/qmltests/Stages/tst_PhoneStage.qml (+4/-0)
tests/qmltests/Stages/tst_TabletStage.qml (+6/-1)
tests/qmltests/tst_Shell.qml (+14/-2)
To merge this branch: bzr merge lp:~unity-team/unity8/lp1475678.surface-occlude
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration 2015-10-27 Needs Fixing on 2015-10-31
Daniel d'Andrada (community) 2015-10-27 Approve on 2015-10-30
Michael Zanetti 2015-10-27 Pending
Michał Sawicz Pending
Review via email: mp+275926@code.launchpad.net

This proposal supersedes a proposal from 2015-10-05.

Commit message

Support server->client visibility change to stop rendering (lp:#1475678)

Description of the change

Support server->client visibility change to stop rendering (lp:#1475678)

 * Are there any related MPs required for this MP to build/function as expected? Please list.
https://code.launchpad.net/~nick-dedekind/qtmir/lp1475678.surface-occlude/+merge/273426
https://code.launchpad.net/~nick-dedekind/unity-api/lp1475678.surface-occlude/+merge/275884
https://code.launchpad.net/~nick-dedekind/qtubuntu/lp1475678.surface-occlude/+merge/273424
https://code.launchpad.net/~mterry/qtmir/no-touch-no-lifecycle/+merge/272791

 * Did you perform an exploratory manual test run of your code change and any related functionality?
 * Did you make sure that your branch does not contain spurious tags?
 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
 * If you changed the UI, has there been a design review?

To post a comment you must log in.
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Stages/TabletStage.qml:

"""
+ // Hiding tiles when their progress is negative or reached the maximum
"""

A TODO item?

review: Needs Information
Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> In qml/Stages/TabletStage.qml:
>
> """
> + // Hiding tiles when their progress is negative or reached the maximum
> """
>
> A TODO item?

Yes. Fixed.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Shouldn't we do something for DesktopStage.qml as well? I quickly checked its code and it seems it doesn't set windows visibility at all.

Things to check:
 - Minimized windows should have their MirSurfaces invisible/occluded.
 - When a window is focused (on foreground, index 0) and maximized, the MirSurfaces of all others should be invisible/occluded

review: Needs Fixing
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

W dniu 13.10.2015 o 14:41, Daniel d'Andrada pisze:
> Things to check:
> - Minimized windows should have their MirSurfaces invisible/occluded.
> - When a window is focused (on foreground, index 0) and maximized, the MirSurfaces of all others should be invisible/occluded

And (maybe later) we should actually try and determine real visibility -
if a window is completely covered by others (we should probably exclude
windows with alpha from this), it should be made invisible/occluded, too.

Lukáš Tinkl (lukas-kde) wrote : Posted in a previous version of this proposal

Left some inline comments; also:

> When a window is focused (on foreground, index 0) and maximized, the MirSurfaces of all others
> should be invisible/occluded

Just wondering what happens in multi monitor situations, should all the other windows/surfaces get occluded too in this case?

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

On 13/10/2015 10:32, Lukáš Tinkl wrote:
>> When a window is focused (on foreground, index 0) and maximized, the MirSurfaces of all others
>> >should be invisible/occluded
> Just wondering what happens in multi monitor situations, should all the other windows/surfaces get occluded too in this case?

Since multimonitor hasn't landed and this branch does not build on top
of it, it can't really address multimonitor issues here. Furthermore, on
the first iteration, windows will be shown all in a single screen (be it
the built-in display or an external monitor), so multimonitor doesn't
really affect occlusion scenarios right now.

Nick Dedekind (nick-dedekind) wrote : Posted in a previous version of this proposal

> Shouldn't we do something for DesktopStage.qml as well? I quickly checked its
> code and it seems it doesn't set windows visibility at all.
>
> Things to check:
> - Minimized windows should have their MirSurfaces invisible/occluded.
> - When a window is focused (on foreground, index 0) and maximized, the
> MirSurfaces of all others should be invisible/occluded

Added.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Could you please add a qml test to tst_DesktopStage to cover the "hide windows behind the maximized one" feature? Logic looks involved enough to warrant a test.

Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

* Open 2 windows so that both are visible. Maximize one, the other will disappear before it's occluded.

* It breaks the alt+tab preview.

review: Needs Fixing
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> * Open 2 windows so that both are visible. Maximize one, the other will
> disappear before it's occluded.
>
> * It breaks the alt+tab preview.

Both seem to be fixed now.

review: Abstain
Michał Sawicz (saviq) : Posted in a previous version of this proposal
review: Needs Fixing
1979. By Nick Dedekind on 2015-10-27

bump libunity-api version

1980. By Michał Sawicz on 2015-10-27

Merge lp:~mterry/unity8/no-touch-no-lifecycle

1981. By Nick Dedekind on 2015-10-28

added clear

Daniel d'Andrada (dandrader) wrote :

In DesktopStage.qml:

"""
if (priv.foregroundMaximizedAppId === model.appId) {
    priv.foregroundMaximizedAppIdIndex = index;
}
else if (priv.foregroundMaximizedAppIdIndex == -1 ||
         (index >= 0 && index <= priv.foregroundMaximizedAppIdIndex)) {
    priv.foregroundMaximizedAppIdIndex = index;
}
"""

Coding style. The "else if" should be in the same line as the closing braces of the previous if.

if () {
} else if () {
}

-------------------------------

In DesktopStage.qml:

"""
(spread.focus && index === spread.highlightedIndex)
"""

Everywhere checks for spread.state == "altTab". You should do the same. spread.focus just happens to be true when the spread is being displayed (alttab state).

--------------------------------

In DesktopStage.qml:

It looks like a circular dependency (binding loop?) as foregroundMaximizedAppId is defined by foregroundMaximizedAppIdIndex (as per the binding assined to it) but foregroundMaximizedAppId also helps define foregroundMaximizedAppIdIndex in updateMaximized().

And it smells fishy that you had to come up with connectMaxEnabled as a way to avoid recursive calls to updateMaximize().

There must be a better way.

-------------------------------

Whenever feasible tests should be manually reproducible. test_applicationsBecomeVisibleWhenOccludingAppRemoved maximizes too applications in a row. You cannot do it manually. It's not a scenario that could happen in "real life". You cannot maximize dialer without also focusing it, which would bring it to the front. Better change the order of the calls so that you maximize dialer right after you launch it.

And you should preferably maximize a window by clicking on its corresponding button in its decoration, like a real user would do, and not by calling an internal function. Again, following the logic of being as close to "real life" code paths as possible. Who knows, maybe clicking on the maximize button causes some subtleties in the QML state which affects what is being tested. And that would not happen when you call the internal function directly.

-----------------------------------

Also all those new tests are taking a data parameter but none of them have a testFoo_data() function. Please remove the parameter.

review: Needs Fixing
1982. By Nick Dedekind on 2015-10-29

remove binding loop

1983. By Nick Dedekind on 2015-10-29

review

Nick Dedekind (nick-dedekind) wrote :

> In DesktopStage.qml:
>
> """
> if (priv.foregroundMaximizedAppId === model.appId) {
> priv.foregroundMaximizedAppIdIndex = index;
> }
> else if (priv.foregroundMaximizedAppIdIndex == -1 ||
> (index >= 0 && index <= priv.foregroundMaximizedAppIdIndex)) {
> priv.foregroundMaximizedAppIdIndex = index;
> }
> """
>
> Coding style. The "else if" should be in the same line as the closing braces
> of the previous if.
>
> if () {
> } else if () {
> }
>
> -------------------------------
>
> In DesktopStage.qml:
>
>
> """
> (spread.focus && index === spread.highlightedIndex)
> """
>
> Everywhere checks for spread.state == "altTab". You should do the same.
> spread.focus just happens to be true when the spread is being displayed
> (alttab state).

Done.

>
> --------------------------------
>
> In DesktopStage.qml:
>
> It looks like a circular dependency (binding loop?) as
> foregroundMaximizedAppId is defined by foregroundMaximizedAppIdIndex (as per
> the binding assined to it) but foregroundMaximizedAppId also helps define
> foregroundMaximizedAppIdIndex in updateMaximized().
>
> And it smells fishy that you had to come up with connectMaxEnabled as a way to
> avoid recursive calls to updateMaximize().
>
> There must be a better way.

Done.

>
> -------------------------------
>
> Whenever feasible tests should be manually reproducible.
> test_applicationsBecomeVisibleWhenOccludingAppRemoved maximizes too
> applications in a row. You cannot do it manually. It's not a scenario that
> could happen in "real life". You cannot maximize dialer without also focusing
> it, which would bring it to the front. Better change the order of the calls so
> that you maximize dialer right after you launch it.
>
> And you should preferably maximize a window by clicking on its corresponding
> button in its decoration, like a real user would do, and not by calling an
> internal function. Again, following the logic of being as close to "real life"
> code paths as possible. Who knows, maybe clicking on the maximize button
> causes some subtleties in the QML state which affects what is being tested.
> And that would not happen when you call the internal function directly.

Apart from the fact you actually can maximize an app underneath another (bug maybe), this is just setting up the initial conditions for the test, ie having 2 maximised applications. I've changed it to maximise after opening the applications.

These are supposed to be unit tests which are testing the stage aren't they, so we shouldn't be testing the decorations.

>
> -----------------------------------
>
> Also all those new tests are taking a data parameter but none of them have a
> testFoo_data() function. Please remove the parameter.

Done.

1984. By Nick Dedekind on 2015-10-29

reverted max change

1985. By Nick Dedekind on 2015-10-29

reverted debug

Daniel d'Andrada (dandrader) wrote :

On 29/10/2015 12:32, Nick Dedekind wrote:
> These are supposed to be unit tests which are testing the stage aren't they, so we shouldn't be testing the decorations.

Kind of. If you skip this part you're missing the
interactions/integration between the decoration and the Stage. As I
previously explained. So your test is not wrong, but it's definitely
less valuable in my opinion.

But ok, won't block because of this.

1986. By Nick Dedekind on 2015-10-29

seq animations

1987. By Nick Dedekind on 2015-10-29

removed foregroundMaximizedAppId

Daniel d'Andrada (dandrader) wrote :

All qmltests passed but for one, unrelated, failure:

FAIL! : qmltestrunner::IndicatorsMenu::test_verticalVelocityDetector() property currentItem
   Actual ():
   Expected ():
   Loc: [/home/dandrader/unity8/lp1475678.surface-occlude/tests/qmltests/Panel/tst_IndicatorsMenu.qml(257)]

It passes if run in "make testIndicatorsMenu" but fails in "make xvfbtestIndicatorsMenu"

Daniel d'Andrada (dandrader) wrote :

Getting this warning several times on the Nexus 7. Don't understand why:

file:///usr/share/unity8//Stages/TabletStage.qml:665: ReferenceError: isFocused is not defined

1988. By Nick Dedekind on 2015-10-30

use maximise as unmaximise if already maximised

Nick Dedekind (nick-dedekind) wrote :

> On 29/10/2015 12:32, Nick Dedekind wrote:
> > These are supposed to be unit tests which are testing the stage aren't they,
> so we shouldn't be testing the decorations.
>
> Kind of. If you skip this part you're missing the
> interactions/integration between the decoration and the Stage. As I
> previously explained. So your test is not wrong, but it's definitely
> less valuable in my opinion.
>
> But ok, won't block because of this.

Valid. I've changed the test to emit the signal from the DecoratedWindow.
I had to fix another bug to do this. where clicking maximise when already maximised will not unmaximise.

Nick Dedekind (nick-dedekind) wrote :

> All qmltests passed but for one, unrelated, failure:
>
> FAIL! : qmltestrunner::IndicatorsMenu::test_verticalVelocityDetector()
> property currentItem
> Actual ():
> Expected ():
> Loc: [/home/dandrader/unity8/lp1475678.surface-
> occlude/tests/qmltests/Panel/tst_IndicatorsMenu.qml(257)]
>
>
> It passes if run in "make testIndicatorsMenu" but fails in "make
> xvfbtestIndicatorsMenu"

Works for me. maybe it's a random failure?

1989. By Nick Dedekind on 2015-10-30

fixed tablet undefined

Nick Dedekind (nick-dedekind) wrote :

> Getting this warning several times on the Nexus 7. Don't understand why:
>
> file:///usr/share/unity8//Stages/TabletStage.qml:665: ReferenceError:
> isFocused is not defined

Doesnt exist in tablet delegate. Fixed.

Daniel d'Andrada (dandrader) wrote :

This qml state fast-forwarding is a real PITA. This is what happens when I maximize the settings application:

http://pastebin.ubuntu.com/13008409/

So we do generate some noise, with apps getting switched unnecessarily (invisible-then-visible-then-invisible).

But not a deal-breaker. Guess we can optimize (or work around) that later.

review: Approve
Daniel d'Andrada (dandrader) wrote :

> This qml state fast-forwarding is a real PITA. This is what happens when I
> maximize the settings application:
>
> http://pastebin.ubuntu.com/13008409/
>
> So we do generate some noise, with apps getting switched unnecessarily
> (invisible-then-visible-then-invisible).
>
> But not a deal-breaker. Guess we can optimize (or work around) that later.

BTW, adding a TODO or FIXME in the code about this issue would be nice, so we don't forget about it.

1990. By Nick Dedekind on 2015-11-04

Fixed shell test orientation defaults

1991. By Nick Dedekind on 2015-11-04

fixed occlusion exceptions

1992. By Nick Dedekind on 2015-11-04

switch around

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-10-21 11:51:11 +0000
3+++ debian/control 2015-11-04 14:53:37 +0000
4@@ -29,7 +29,7 @@
5 libqt5xmlpatterns5-dev,
6 libsystemsettings-dev,
7 libudev-dev,
8- libunity-api-dev (>= 7.101),
9+ libunity-api-dev (>= 7.102),
10 libusermetricsoutput1-dev,
11 libxcb1-dev,
12 pkg-config,
13
14=== modified file 'qml/Stages/DesktopStage.qml'
15--- qml/Stages/DesktopStage.qml 2015-11-04 14:53:36 +0000
16+++ qml/Stages/DesktopStage.qml 2015-11-04 14:53:37 +0000
17@@ -68,6 +68,22 @@
18 var index = indexOf(focusedAppId);
19 return index >= 0 && index < appRepeater.count ? appRepeater.itemAt(index) : null
20 }
21+ property int foregroundMaximizedAppIdIndex: -1
22+
23+ function updateForegroundMaximizedApp() {
24+ for (var i = 0; i < appRepeater.count; i++) {
25+ var item = appRepeater.itemAt(i);
26+
27+ if (item && item.visuallyMaximized) {
28+ var app = ApplicationManager.get(i);
29+ if (app) {
30+ foregroundMaximizedAppIdIndex = i;
31+ return;
32+ }
33+ }
34+ }
35+ foregroundMaximizedAppIdIndex = -1;
36+ }
37
38 function indexOf(appId) {
39 for (var i = 0; i < ApplicationManager.count; i++) {
40@@ -114,8 +130,12 @@
41 model: ApplicationManager
42 objectName: "appRepeater"
43
44+ onItemAdded: priv.updateForegroundMaximizedApp()
45+ onItemRemoved: priv.updateForegroundMaximizedApp()
46+
47 delegate: FocusScope {
48 id: appDelegate
49+ objectName: "stageDelegate_" + model.appId
50 z: ApplicationManager.count - index
51 y: units.gu(3)
52 width: units.gu(60)
53@@ -126,12 +146,25 @@
54 property bool minimized: false
55 property bool animationsEnabled: true
56
57+ property bool visuallyMaximized: false
58+ property bool visuallyMinimized: false
59+
60 onFocusChanged: {
61 if (focus && ApplicationManager.focusedApplicationId !== model.appId) {
62 ApplicationManager.focusApplication(model.appId);
63 }
64 }
65
66+ onZChanged: priv.updateForegroundMaximizedApp()
67+ onVisuallyMaximizedChanged: priv.updateForegroundMaximizedApp()
68+
69+ visible: !visuallyMinimized &&
70+ !greeter.fullyShown &&
71+ (priv.foregroundMaximizedAppIdIndex === -1 || priv.foregroundMaximizedAppIdIndex >= index) ||
72+ (spread.state == "altTab" && index === spread.highlightedIndex)
73+
74+ onVisibleChanged: console.log("VISIBLE", model.appId, visible)
75+
76 Binding {
77 target: ApplicationManager.get(index)
78 property: "requestedState"
79@@ -161,23 +194,60 @@
80
81 states: [
82 State {
83- name: "normal"; when: !appDelegate.maximized && !appDelegate.minimized
84+ name: "normal";
85+ when: !appDelegate.maximized && !appDelegate.minimized
86+ PropertyChanges {
87+ target: appDelegate;
88+ visuallyMinimized: false;
89+ visuallyMaximized: false
90+ }
91 },
92 State {
93 name: "maximized"; when: appDelegate.maximized
94- PropertyChanges { target: appDelegate; x: 0; y: 0; width: root.width; height: root.height }
95+ PropertyChanges {
96+ target: appDelegate;
97+ x: 0; y: 0;
98+ width: root.width; height: root.height;
99+ visuallyMinimized: false;
100+ visuallyMaximized: true
101+ }
102 },
103 State {
104 name: "minimized"; when: appDelegate.minimized
105- PropertyChanges { target: appDelegate; x: -appDelegate.width / 2; scale: units.gu(5) / appDelegate.width; opacity: 0 }
106+ PropertyChanges {
107+ target: appDelegate;
108+ x: -appDelegate.width / 2;
109+ scale: units.gu(5) / appDelegate.width;
110+ opacity: 0
111+ visuallyMinimized: true;
112+ visuallyMaximized: false
113+ }
114 }
115 ]
116 transitions: [
117 Transition {
118- from: "maximized,minimized,normal,"
119- to: "maximized,minimized,normal,"
120- enabled: appDelegate.animationsEnabled
121- PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale" }
122+ to: "normal"
123+ enabled: appDelegate.animationsEnabled
124+ PropertyAction { target: appDelegate; properties: "visuallyMinimized,visuallyMaximized" }
125+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
126+ },
127+ Transition {
128+ to: "maximized"
129+ enabled: appDelegate.animationsEnabled
130+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
131+ SequentialAnimation {
132+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
133+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
134+ }
135+ },
136+ Transition {
137+ to: "minimized"
138+ enabled: appDelegate.animationsEnabled
139+ PropertyAction { target: appDelegate; property: "visuallyMaximized" }
140+ SequentialAnimation {
141+ PropertyAnimation { target: appDelegate; properties: "x,y,opacity,width,height,scale,opacity" }
142+ PropertyAction { target: appDelegate; property: "visuallyMinimized" }
143+ }
144 },
145 Transition {
146 from: ""
147@@ -223,7 +293,7 @@
148 focus: true
149
150 onClose: ApplicationManager.stopApplication(model.appId)
151- onMaximize: appDelegate.maximize()
152+ onMaximize: appDelegate.maximized ? appDelegate.unmaximize() : appDelegate.maximize()
153 onMinimize: appDelegate.minimize()
154 onDecorationPressed: { ApplicationManager.focusApplication(model.appId) }
155 }
156
157=== modified file 'qml/Stages/PhoneStage.qml'
158--- qml/Stages/PhoneStage.qml 2015-11-04 14:53:36 +0000
159+++ qml/Stages/PhoneStage.qml 2015-11-04 14:53:37 +0000
160@@ -20,6 +20,7 @@
161 import Unity.Application 0.1
162 import Unity.Session 0.1
163 import Utils 0.1
164+import Powerd 0.1
165 import "../Components"
166
167 AbstractStage {
168@@ -182,7 +183,8 @@
169 }
170 }
171
172- property bool focusedAppDelegateIsDislocated: focusedAppDelegate && focusedAppDelegate.x !== 0
173+ property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
174+ (focusedAppDelegate.x !== 0 || focusedAppDelegate.xBehavior.running)
175
176 function indexOf(appId) {
177 for (var i = 0; i < root.applicationManager.count; i++) {
178@@ -473,7 +475,6 @@
179
180 property var xBehavior: xBehavior
181 Behavior on x {
182- id: xBehavior
183 enabled: root.spreadEnabled &&
184 !spreadView.active &&
185 !snapAnimation.running &&
186@@ -481,6 +482,7 @@
187 priv.animateX &&
188 !root.beingResized
189 UbuntuNumberAnimation {
190+ id: xBehavior
191 duration: UbuntuAnimation.BriskDuration
192 }
193 }
194@@ -520,9 +522,18 @@
195 return progress;
196 }
197
198- // Hiding tiles when their progress is negative or reached the maximum
199- visible: (progress >= 0 && progress < 1.7)
200- || (isDash && priv.focusedAppDelegateIsDislocated)
201+ // Hide tile when progress is such that it will be off screen.
202+ property bool occluded: {
203+ if (spreadView.active && (progress >= 0 && progress < 1.7)) return false;
204+ else if (!spreadView.active && isFocused) return false;
205+ else if (xBehavior.running) return false;
206+ else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
207+ return true;
208+ }
209+
210+ visible: Powerd.status == Powerd.On &&
211+ !greeter.fullyShown &&
212+ !occluded
213
214 shellOrientationAngle: root.shellOrientationAngle
215 shellOrientation: root.shellOrientation
216
217=== modified file 'qml/Stages/TabletStage.qml'
218--- qml/Stages/TabletStage.qml 2015-11-04 14:53:36 +0000
219+++ qml/Stages/TabletStage.qml 2015-11-04 14:53:37 +0000
220@@ -1,4 +1,4 @@
221-/*
222+/*
223 * Copyright (C) 2014-2015 Canonical, Ltd.
224 *
225 * This program is free software; you can redistribute it and/or modify
226@@ -19,6 +19,7 @@
227 import Ubuntu.Gestures 0.1
228 import Unity.Application 0.1
229 import Utils 0.1
230+import Powerd 0.1
231 import "../Components"
232
233 AbstractStage {
234@@ -146,6 +147,8 @@
235 }
236 }
237
238+ property bool focusedAppDelegateIsDislocated: focusedAppDelegate &&
239+ (focusedAppDelegate.dragOffset !== 0 || focusedAppDelegate.xTranslateAnimating)
240 function indexOf(appId) {
241 for (var i = 0; i < ApplicationManager.count; i++) {
242 if (ApplicationManager.get(i).appId == appId) {
243@@ -659,6 +662,19 @@
244 return tileProgress;
245 }
246
247+ // TODO: Hiding tile when progress is such that it will be off screen.
248+ property bool occluded: {
249+ if (spreadView.active) return false;
250+ else if (spreadTile.active) return false;
251+ else if (xTranslateAnimating) return false;
252+ else if (z <= 1 && priv.focusedAppDelegateIsDislocated) return false;
253+ return true;
254+ }
255+
256+ visible: Powerd.status == Powerd.On &&
257+ !greeter.fullyShown &&
258+ !occluded
259+
260 animatedProgress: {
261 if (spreadView.phase == 0 && (spreadTile.active || spreadView.nextInStack == index)) {
262 if (progress < spreadView.positionMarker1) {
263
264=== modified file 'qml/Stages/TransformedTabletSpreadDelegate.qml'
265--- qml/Stages/TransformedTabletSpreadDelegate.qml 2015-03-06 04:44:11 +0000
266+++ qml/Stages/TransformedTabletSpreadDelegate.qml 2015-11-04 14:53:37 +0000
267@@ -48,6 +48,7 @@
268 property bool isInSideStage: false
269
270 property int dragOffset: 0
271+ readonly property alias xTranslateAnimating: xTranslateAnimation.running
272
273 dropShadow: spreadView.active ||
274 (active
275@@ -144,6 +145,7 @@
276 spreadView.animateX &&
277 !spreadView.beingResized
278 UbuntuNumberAnimation {
279+ id: xTranslateAnimation
280 duration: UbuntuAnimation.FastDuration
281 }
282 }
283
284=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
285--- tests/mocks/Unity/Application/MirSurface.cpp 2015-09-25 12:13:13 +0000
286+++ tests/mocks/Unity/Application/MirSurface.cpp 2015-11-04 14:53:37 +0000
287@@ -31,7 +31,7 @@
288 , m_screenshotUrl(screenshot)
289 , m_qmlFilePath(qmlFilePath)
290 , m_live(true)
291- , m_viewCount(0)
292+ , m_visible(true)
293 , m_activeFocus(false)
294 , m_width(-1)
295 , m_height(-1)
296@@ -73,6 +73,11 @@
297 return m_live;
298 }
299
300+bool MirSurface::visible() const
301+{
302+ return m_visible;
303+}
304+
305 void MirSurface::setLive(bool live)
306 {
307 // qDebug().nospace() << "MirSurface::setLive("<<live<<") " << name();
308@@ -82,7 +87,7 @@
309 m_live = live;
310 Q_EMIT liveChanged(live);
311
312- if (!m_live && m_viewCount == 0) {
313+ if (!m_live && m_views.count() == 0) {
314 deleteLater();
315 }
316 }
317@@ -120,27 +125,49 @@
318 Q_EMIT orientationAngleChanged(angle);
319 }
320
321-void MirSurface::incrementViewCount()
322+
323+
324+void MirSurface::registerView(qintptr viewId)
325 {
326- ++m_viewCount;
327-// qDebug().nospace() << "MirSurface::incrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
328+ m_views.insert(viewId, MirSurface::View{false});
329+// qDebug().nospace() << "MirSurface[" << name() << "]::registerView(" << viewId << ")"
330+// << " after=" << m_views.count();
331 }
332
333-void MirSurface::decrementViewCount()
334+void MirSurface::unregisterView(qintptr viewId)
335 {
336- --m_viewCount;
337-// qDebug().nospace() << "MirSurface::decrementViewCount() viewCount(after)=" << m_viewCount << " " << name();
338-
339- Q_ASSERT(m_viewCount >= 0);
340-
341- if (!m_live && m_viewCount == 0) {
342+// qDebug().nospace() << "MirSurface[" << name() << "]::unregisterView(" << viewId << ")"
343+// << " after=" << m_views.count() << " live=" << m_live;
344+ m_views.remove(viewId);
345+ if (!m_live && m_views.count() == 0) {
346 deleteLater();
347 }
348-}
349-
350-int MirSurface::viewCount() const
351-{
352- return m_viewCount;
353+ updateVisibility();
354+}
355+
356+void MirSurface::setViewVisibility(qintptr viewId, bool visible)
357+{
358+ if (!m_views.contains(viewId)) return;
359+
360+ m_views[viewId].visible = visible;
361+ updateVisibility();
362+}
363+
364+void MirSurface::updateVisibility()
365+{
366+ bool newVisible = false;
367+ QHashIterator<qintptr, View> i(m_views);
368+ while (i.hasNext()) {
369+ i.next();
370+ newVisible |= i.value().visible;
371+ }
372+
373+ if (newVisible != visible()) {
374+// qDebug().nospace() << "MirSurface[" << name() << "]::updateVisibility(" << newVisible << ")";
375+
376+ m_visible = newVisible;
377+ Q_EMIT visibleChanged(m_visible);
378+ }
379 }
380
381 bool MirSurface::activeFocus() const
382
383=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
384--- tests/mocks/Unity/Application/MirSurface.h 2015-09-02 10:35:16 +0000
385+++ tests/mocks/Unity/Application/MirSurface.h 2015-11-04 14:53:37 +0000
386@@ -19,6 +19,7 @@
387
388 #include <QObject>
389 #include <QUrl>
390+#include <QHash>
391
392 // unity-api
393 #include <unity/shell/application/MirSurfaceInterface.h>
394@@ -58,6 +59,8 @@
395
396 bool live() const override;
397
398+ bool visible() const override;
399+
400 Mir::OrientationAngle orientationAngle() const override;
401 void setOrientationAngle(Mir::OrientationAngle) override;
402
403@@ -66,9 +69,10 @@
404
405 Q_INVOKABLE void setLive(bool live);
406
407- void incrementViewCount();
408- void decrementViewCount();
409- int viewCount() const;
410+ void registerView(qintptr viewId);
411+ void unregisterView(qintptr viewId);
412+ void setViewVisibility(qintptr viewId, bool visible);
413+ int viewCount() const { return m_views.count(); }
414
415 int width() const;
416 int height() const;
417@@ -97,6 +101,8 @@
418 void activeFocusChanged(bool);
419
420 private:
421+ void updateVisibility();
422+
423 const QString m_name;
424 const Mir::Type m_type;
425 Mir::State m_state;
426@@ -104,10 +110,14 @@
427 QUrl m_screenshotUrl;
428 QUrl m_qmlFilePath;
429 bool m_live;
430- int m_viewCount;
431+ bool m_visible;
432 bool m_activeFocus;
433 int m_width;
434 int m_height;
435+ struct View {
436+ bool visible;
437+ };
438+ QHash<qintptr, View> m_views;
439 };
440
441 #endif // MOCK_MIR_SURFACE_H
442
443=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.cpp'
444--- tests/mocks/Unity/Application/MirSurfaceItem.cpp 2015-09-25 12:13:13 +0000
445+++ tests/mocks/Unity/Application/MirSurfaceItem.cpp 2015-11-04 14:53:37 +0000
446@@ -44,6 +44,8 @@
447 Qt::ExtraButton5 | Qt::ExtraButton6 | Qt::ExtraButton7 | Qt::ExtraButton8 |
448 Qt::ExtraButton9 | Qt::ExtraButton10 | Qt::ExtraButton11 |
449 Qt::ExtraButton12 | Qt::ExtraButton13);
450+
451+ connect(this, &QQuickItem::visibleChanged, this, &MirSurfaceItem::updateMirSurfaceVisibility);
452 }
453
454 MirSurfaceItem::~MirSurfaceItem()
455@@ -194,17 +196,18 @@
456 m_qmlContentComponent = nullptr;
457
458 disconnect(m_qmlSurface, nullptr, this, nullptr);
459- m_qmlSurface->decrementViewCount();
460+ m_qmlSurface->unregisterView((qintptr)this);
461 }
462
463 m_qmlSurface = static_cast<MirSurface*>(surface);
464
465 if (m_qmlSurface) {
466- m_qmlSurface->incrementViewCount();
467+ m_qmlSurface->registerView((qintptr)this);
468
469 m_qmlSurface->setActiveFocus(hasActiveFocus());
470
471 updateSurfaceSize();
472+ updateMirSurfaceVisibility();
473
474 connect(m_qmlSurface, &MirSurface::orientationAngleChanged, this, &MirSurfaceItem::orientationAngleChanged);
475 connect(m_qmlSurface, &MirSurface::screenshotUrlChanged, this, &MirSurfaceItem::updateScreenshot);
476@@ -253,6 +256,13 @@
477 }
478 }
479
480+void MirSurfaceItem::updateMirSurfaceVisibility()
481+{
482+ if (!m_qmlSurface) return;
483+
484+ m_qmlSurface->setViewVisibility((qintptr)this, isVisible());
485+}
486+
487 void MirSurfaceItem::setConsumesInput(bool value)
488 {
489 if (m_consumesInput != value) {
490
491=== modified file 'tests/mocks/Unity/Application/MirSurfaceItem.h'
492--- tests/mocks/Unity/Application/MirSurfaceItem.h 2015-09-19 07:37:52 +0000
493+++ tests/mocks/Unity/Application/MirSurfaceItem.h 2015-11-04 14:53:37 +0000
494@@ -87,6 +87,7 @@
495 private Q_SLOTS:
496 void onComponentStatusChanged(QQmlComponent::Status status);
497 void updateScreenshot(QUrl screenshot);
498+ void updateMirSurfaceVisibility();
499
500 private:
501 void createQmlContentItem();
502
503=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
504--- tests/qmltests/Stages/tst_DesktopStage.qml 2015-09-17 12:25:29 +0000
505+++ tests/qmltests/Stages/tst_DesktopStage.qml 2015-11-04 14:53:37 +0000
506@@ -23,24 +23,31 @@
507 import Utils 0.1
508
509 import "../../../qml/Stages"
510+import "../../../qml/Components"
511
512 Item {
513 id: root
514 width: desktopStageLoader.width + controls.width
515 height: desktopStageLoader.height
516
517+ property var greeter: { fullyShown: true }
518+
519 Binding {
520 target: MouseTouchAdaptor
521 property: "enabled"
522 value: false
523 }
524
525- Component.onCompleted: {
526+ Component.onCompleted: resetGeometry()
527+
528+ function resetGeometry() {
529 // ensures apps which are tested decorations are in view.
530+ WindowStateStorage.clear();
531 WindowStateStorage.geometry = {
532 'unity8-dash': Qt.rect(0, units.gu(3), units.gu(50), units.gu(40)),
533 'dialer-app': Qt.rect(units.gu(51), units.gu(3), units.gu(50), units.gu(40)),
534 'camera-app': Qt.rect(0, units.gu(44), units.gu(50), units.gu(40)),
535+ 'gallery-app': Qt.rect(units.gu(51), units.gu(44), units.gu(50), units.gu(40))
536 }
537 }
538
539@@ -61,6 +68,7 @@
540 Component.onDestruction: {
541 desktopStageLoader.itemDestroyed = true;
542 }
543+ orientations: Orientations {}
544 }
545 }
546 }
547@@ -110,14 +118,14 @@
548
549 desktopStageLoader.active = true;
550 tryCompare(desktopStageLoader, "status", Loader.Ready);
551+ root.resetGeometry();
552 }
553
554 function killAllRunningApps() {
555- while (ApplicationManager.count > 1) {
556- var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
557- ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
558+ while (ApplicationManager.count > 0) {
559+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId);
560 }
561- compare(ApplicationManager.count, 1)
562+ compare(ApplicationManager.count, 0)
563 }
564
565 function waitUntilAppSurfaceShowsUp(appId) {
566@@ -210,5 +218,73 @@
567 tap(toAppDecoration);
568 tryCompare(ApplicationManager.findApplication(data.apps[data.focusTo]).session.surface, "activeFocus", true);
569 }
570+
571+ function test_minimizeApplicationHidesSurface() {
572+ var dashApp = startApplication("unity8-dash");
573+
574+ var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
575+ verify(dashDelegate);
576+
577+ findChild(dashDelegate, "decoratedWindow").minimize();
578+ tryCompare(dashApp.session.surface, "visible", false);
579+ }
580+
581+ function test_maximizeApplicationHidesSurfacesBehindIt() {
582+ var dashApp = startApplication("unity8-dash");
583+ var dialerApp = startApplication("dialer-app");
584+ var cameraApp = startApplication("camera-app");
585+
586+ var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
587+ verify(dashDelegate);
588+ var dialerDelegate = findChild(desktopStage, "stageDelegate_dialer-app");
589+ verify(dialerDelegate);
590+ var cameraDelegate = findChild(desktopStage, "stageDelegate_camera-app");
591+ verify(cameraDelegate);
592+
593+ // maximize
594+ findChild(dialerDelegate, "decoratedWindow").maximize();
595+ tryCompare(dialerDelegate, "visuallyMaximized", true);
596+
597+ tryCompare(dashApp.session.surface, "visible", false);
598+ compare(cameraApp.session.surface.visible, true);
599+
600+ // restore
601+ findChild(dialerDelegate, "decoratedWindow").maximize();
602+ compare(dashApp.session.surface.visible, true);
603+ compare(cameraApp.session.surface.visible, true);
604+ }
605+
606+ function test_applicationsBecomeVisibleWhenOccludingAppRemoved() {
607+ var dashApp = startApplication("unity8-dash");
608+ var dashDelegate = findChild(desktopStage, "stageDelegate_unity8-dash");
609+ verify(dashDelegate);
610+
611+ var dialerApp = startApplication("dialer-app");
612+ var dialerDelegate = findChild(desktopStage, "stageDelegate_dialer-app");
613+ verify(dialerDelegate);
614+
615+ var cameraApp = startApplication("camera-app");
616+ var cameraDelegate = findChild(desktopStage, "stageDelegate_camera-app");
617+ verify(cameraDelegate);
618+ findChild(dialerDelegate, "decoratedWindow").maximize();
619+
620+ var galleryApp = startApplication("gallery-app");
621+ var galleryDelegate = findChild(desktopStage, "stageDelegate_gallery-app");
622+ verify(galleryDelegate);
623+ findChild(galleryDelegate, "decoratedWindow").maximize();
624+
625+ tryCompare(dialerDelegate, "visuallyMaximized", true);
626+ tryCompare(galleryDelegate, "visuallyMaximized", true);
627+
628+ tryCompare(dashApp.session.surface, "visible", false);
629+ tryCompare(dialerApp.session.surface, "visible", false);
630+ tryCompare(cameraApp.session.surface, "visible", false);
631+
632+ ApplicationManager.stopApplication("gallery-app");
633+
634+ compare(cameraApp.session.surface.visible, true);
635+ tryCompare(dialerApp.session.surface, "visible", true);
636+ tryCompare(dashApp.session.surface, "visible", false); // still occluded by maximised dialer
637+ }
638 }
639 }
640
641=== modified file 'tests/qmltests/Stages/tst_PhoneStage.qml'
642--- tests/qmltests/Stages/tst_PhoneStage.qml 2015-11-04 14:53:36 +0000
643+++ tests/qmltests/Stages/tst_PhoneStage.qml 2015-11-04 14:53:37 +0000
644@@ -18,6 +18,7 @@
645 import QtTest 1.0
646 import Unity.Test 0.1 as UT
647 import ".."
648+import "../../../qml/Components"
649 import "../../../qml/Stages"
650 import Ubuntu.Components 0.1
651 import Unity.Application 0.1
652@@ -26,6 +27,8 @@
653 width: units.gu(70)
654 height: units.gu(70)
655
656+ property var greeter: { fullyShown: true }
657+
658 PhoneStage {
659 id: phoneStage
660 anchors { fill: parent; rightMargin: units.gu(30) }
661@@ -34,6 +37,7 @@
662 maximizedAppTopMargin: units.gu(3) + units.dp(2)
663 interactive: true
664 shellOrientation: Qt.PortraitOrientation
665+ orientations: Orientations {}
666 }
667
668 Binding {
669
670=== modified file 'tests/qmltests/Stages/tst_TabletStage.qml'
671--- tests/qmltests/Stages/tst_TabletStage.qml 2015-11-04 14:53:36 +0000
672+++ tests/qmltests/Stages/tst_TabletStage.qml 2015-11-04 14:53:37 +0000
673@@ -30,6 +30,8 @@
674 width: tabletStageLoader.width + controls.width
675 height: tabletStageLoader.height
676
677+ property var greeter: { fullyShown: true }
678+
679 Loader {
680 id: tabletStageLoader
681
682@@ -53,7 +55,10 @@
683 shellOrientation: Qt.LandscapeOrientation
684 nativeWidth: width
685 nativeHeight: height
686- orientations: Orientations{} // Defaults are fine for testing
687+ orientations: Orientations {
688+ native_: Qt.LandscapeOrientation
689+ primary: Qt.LandscapeOrientation
690+ }
691 focus: true
692 }
693 }
694
695=== modified file 'tests/qmltests/tst_Shell.qml'
696--- tests/qmltests/tst_Shell.qml 2015-11-04 14:53:36 +0000
697+++ tests/qmltests/tst_Shell.qml 2015-11-04 14:53:37 +0000
698@@ -61,6 +61,10 @@
699
700 anchors.centerIn: parent
701
702+ property int shellOrientation: Qt.PortraitOrientation
703+ property int nativeOrientation: Qt.PortraitOrientation
704+ property int primaryOrientation: Qt.PortraitOrientation
705+
706 state: "phone"
707 states: [
708 State {
709@@ -77,6 +81,9 @@
710 target: shellLoader
711 width: units.gu(100)
712 height: units.gu(71)
713+ shellOrientation: Qt.LandscapeOrientation
714+ nativeOrientation: Qt.LandscapeOrientation
715+ primaryOrientation: Qt.LandscapeOrientation
716 }
717 },
718 State {
719@@ -99,8 +106,13 @@
720 Shell {
721 id: __shell
722 usageScenario: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
723- orientation: Qt.PortraitOrientation
724- orientations: Orientations{} // Defaults are fine for testing
725+ nativeWidth: width
726+ nativeHeight: height
727+ orientation: shellLoader.shellOrientation
728+ orientations: Orientations {
729+ native_: shellLoader.nativeOrientation
730+ primary: shellLoader.primaryOrientation
731+ }
732 Component.onDestruction: {
733 shellLoader.itemDestroyed = true;
734 }

Subscribers

People subscribed via source and target branches