Merge lp:~unity-team/unity8/right-edge-2 into lp:unity8

Proposed by Michał Sawicz
Status: Merged
Approved by: Michał Sawicz
Approved revision: 755
Merged at revision: 798
Proposed branch: lp:~unity-team/unity8/right-edge-2
Merge into: lp:unity8
Diff against target: 4314 lines (+2423/-902)
42 files modified
debian/changelog (+7/-0)
debian/control (+7/-4)
debian/unity8.install (+1/-1)
plugins/Utils/CMakeLists.txt (+2/-1)
plugins/Utils/easingcurve.cpp (+67/-0)
plugins/Utils/easingcurve.h (+70/-0)
plugins/Utils/plugin.cpp (+2/-0)
qml/CMakeLists.txt (+1/-1)
qml/Components/ApplicationManagerWrapper.qml (+0/-169)
qml/Components/ApplicationScreenshot.qml (+0/-61)
qml/Components/ApplicationsModelStageFiltered.qml (+0/-49)
qml/Components/ResponsiveFlowView.qml (+3/-10)
qml/Dash/Apps/RunningApplicationTile.qml (+15/-25)
qml/Dash/Apps/RunningApplicationsGrid.qml (+5/-20)
qml/Dash/DashApps.qml (+4/-5)
qml/Dash/GenericScopeView.qml (+2/-2)
qml/Launcher/Launcher.qml (+2/-1)
qml/Panel/Panel.qml (+1/-1)
qml/Shell.qml (+231/-333)
qml/SideStage/SideStage.qml (+0/-73)
qml/Stages/PhoneStage.qml (+499/-0)
qml/Stages/SpreadDelegate.qml (+55/-0)
qml/Stages/StageWithSideStage.qml (+407/-0)
qml/Stages/SwitchingApplicationImage.qml (+81/-0)
qml/Stages/TransformedSpreadDelegate.qml (+327/-0)
tests/autopilot/unity8/shell/emulators/dash.py (+1/-4)
tests/autopilot/unity8/shell/emulators/main_window.py (+1/-1)
tests/mocks/Unity/Application/ApplicationInfo.cpp (+9/-0)
tests/mocks/Unity/Application/ApplicationInfo.h (+2/-0)
tests/mocks/Unity/Application/ApplicationManager.cpp (+109/-3)
tests/mocks/Unity/Application/ApplicationManager.h (+16/-0)
tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp (+74/-0)
tests/mocks/Unity/Application/ApplicationScreenshotProvider.h (+34/-0)
tests/mocks/Unity/Application/CMakeLists.txt (+1/-0)
tests/mocks/Unity/Application/plugin.cpp (+18/-3)
tests/mocks/Unity/Application/plugin.h (+1/-0)
tests/plugins/Unity/Launcher/launchermodeltest.cpp (+5/-0)
tests/qmltests/CMakeLists.txt (+1/-1)
tests/qmltests/Components/tst_ResponsiveFlowView.qml (+1/-2)
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml (+57/-124)
tests/qmltests/Stages/tst_PhoneStage.qml (+288/-0)
tests/qmltests/tst_Shell.qml (+16/-8)
To merge this branch: bzr merge lp:~unity-team/unity8/right-edge-2
Reviewer Review Type Date Requested Status
Michał Sawicz Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Leo Arias (community) autopilot code review Needs Information
Mirco Müller Pending
Gerry Boland Pending
Albert Astals Cid Pending
Ubuntu Unity PS integration team packaging Pending
Vesa Rautiainen Pending
Review via email: mp+213172@code.launchpad.net

This proposal supersedes a proposal from 2014-02-04.

Commit message

Implement the right edge app switching with an App Spread.

* Reworks interaction with ApplicationManager (This will end SurfaceFlinger support)
* Reworks Stage and SideStage
* Implements the App Spread for the Phone form factor right edge
* Reimplements the overlay mode side stage for the tablet form factor

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~mzanetti/unity-api/new-screenshot-and-focusing-api/+merge/199810
https://code.launchpad.net/~mzanetti/unity-mir/screenshotting-focusing-api/+merge/199811
https://code.launchpad.net/~mzanetti/unity-scopes-shell/activate-appid-2/+merge/205443
https://code.launchpad.net/~gerboland/unity-mir/phone-surface-bad-position/+merge/212695
https://code.launchpad.net/~mzanetti/qtubuntu/drop-appmanager/+merge/212697

Here are packages for everything related: https://chinstrap.canonical.com/~mzanetti/right-edge-pkgs/

 * Did you perform an exploratory manual test run of your code change and any related functionality?

Yes (MWC image)

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

Yes

 * If you changed the UI, has there been a design review?

lots of them :)

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

1785 +// Rectangle { anchors.fill: parent; color: "blue"; opacity: 0.5 }

Leftover

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

1901 + onApplicationAdded: {
1902 + priv.startingAppId = appId;
1903 +// var application = ApplicationManager.findApplication(appId)
1904 +// print("Application added:", appId, application.state)
1905 +// if (application.stage == ApplicationInfoInterface.SideStage) {
1906 +// priv.sideStageAppId = appId;
1907 +// mainStageImage.switchTo(application)
1908 +// mainStageImage.visible = true;
1909 +// } else if (application.stage == ApplicationInfoInterface.MainStage) {
1910 +// priv.mainStageAppId = appId;
1911 +// sideStageImage.switchTo(application)
1912 +// sideStageImage.visible = true;
1913 +// }
1914 + }

Do we want to have this commented-out code?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Please mention in the commit message that this branch marks the end of SurfaceFlinger support, so unity8 is not Mir only.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

> Please mention in the commit message that this branch marks the end of
> SurfaceFlinger support, so unity8 is not Mir only.
I meant: *now* Mir only
/hittip tsdgeos

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

In PhoneStage.qml:

"""
+ property bool fullscreen: ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).fullscreen
"""

That generates errors, polluting the console, when there's no focused application. Having it the following way is more concise and solves this issue:

property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false

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

Likewise here:

"""
        PropertyAction { target: fadeInScreenshotImage; property: "source"; value: ApplicationManager.findApplication(priv.newFocusedAppId).screenshot }
"""

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

I'm getting the following error on start up:

qml/Stages/PhoneStage.qml:356: ReferenceError: shift is not defined

Which is solved by:

"""
         SequentialAnimation {
             id: snapAnimation
- property int targetContentX: -shift
+ property int targetContentX: -spreadView.shift

             UbuntuNumberAnimation {
                 target: spreadView
"""

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ qml/Shell.qml
+ property bool sideStageEnabled: shell.width >= units.gu(60)
Can be readonly.

+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
Do no harm here either

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

And this other error on start up:

qml/Stages/PhoneStage.qml:47: TypeError: Cannot read property 'screenshot' of null

"""
                mainScreenshotImage.src = ApplicationManager.get(0).screenshot
"""

So gotta check if there's indeed a running app before querying its screenshot.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+ readonly property bool applicationRunning: ApplicationManager.focusedApplicationId.length > 0
I don't see this used anywhere actually.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Functional bugs (phone testing only)
1. I launch Contacts app from dash. Wait for it to appear. Left edge swipe it away. I'm not able to bring back Contacts by tapping its preview, nor its dahs or launcher icon.

2. I launch Contacts app from dash. Wait for it to appear. Left edge swipe it away. I then launch Dialer app from dash, I don't get a starting animation: instead Contacts app immediately shown, and eventually Dialer pops up on top

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

3. With 2 apps open, doing a full right edge swipe, the animation is linked to my finger distance from the right edge, until about 70% of the way, when it just stops. Then only finger release completes the animation. It feels like the animation locks up. Did Design want this?

4. Have app open and active. Press power to turn off display, and again to show greeter. Do a slow right-edge swipe - a black rectangle is revealed. Only on finger release does the app appear.

5. I've 2 apps open. I open a third from the dash. Once it has appeared (giving it a second more just in case), I start a right edge gesture. I only see 2 apps in the spread, the latest one has disappeared

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Sorry about the hopeless picture, but this was something minor I noticed:
http://imgur.com/O6EFlQh
I opened 3 apps, dialer, contacts & system settings. With the order in the spread of contacts, settings, dialer, I grabbed the dialer screenshot and brought it as far left as I could. I noticed that a bit of the system settings image could be made visible over the dialer. See the image above, I marked it in red

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

6. With spread open, opening launcher and tapping dash icon does nothing

7. Have a couple of apps open, and engage the spread. Now open the (as example) sound indicator, and tap the "Sound settings" entry. Indicators close, but spread remains. Tapping the system settings does bring me to the correct screen.

8. Strangely, if I open System Settings app via indicators (same steps as in 7 above), it often opens with incorrect x position of the surface (i.e there's a 2-3GU gap on the left). Need to check with trunk.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

9. Have system settings & dialer app open, dialer app to the front. Open indicators, select Sound and tap "Sound settings". You see the dialer app slide to the right, but a black rectangle remains, until suddenly system settings appears

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

http://imgur.com/gz5p3Gg I managed to get somehow, not sure how

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

> http://imgur.com/gz5p3Gg I managed to get somehow, not sure how
Ah I had started Gmail app. Seems it took an ultra long time to start up, so appeared in the running apps list, but no screenshot for it existed?? So the delegate was too narrow?

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

10. Starting Gallery app does not make panel hide (which it should)

11. Quick right edge swipe can be a little flickery. I'd forgive this however, I know what's going on.

12. With multiple apps open, bring up the OSK somehow. Then do right edge swipe to show spread. OSK stuck open

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

13. seems running apps icons are not all the same width: http://imgur.com/altNLT7 The second row doesn't match the first, column-wise. Note that not all apps are fullscreen any more.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Question: in the spread, the edges of the transformed screenshots are not antialiased. Turning on smoothing bad?

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

14. this is a tricky one to reproduce: open like 8+ apps. Right edge to open spread. Flick list to the left so you see top-most screenshot. Now flick it again, and while it is bouncing, do a right-edge swipe. When I do this, I switch to the second last app in the list (like the quick right-edge Alt-Tab swipe)

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

nitpick: with 8+ apps open, when you flick spread to the the left, so you see top-most screenshot, the framerate drops a bit. Optimisation can be left for later naturally

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

15. with spread open (3+ apps running), a long left swipe causes launcher to open, then close again. Would be better if it stayed open IMO. Short left swipe open it fine

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> > Please mention in the commit message that this branch marks the end of
> > SurfaceFlinger support, so unity8 is not Mir only.
> I meant: *now* Mir only
> /hittip tsdgeos

done

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> In PhoneStage.qml:
>
> """
> + property bool fullscreen: ApplicationManager.findApplication(ApplicationMana
> ger.focusedApplicationId).fullscreen
> """
>
> That generates errors, polluting the console, when there's no focused
> application. Having it the following way is more concise and solves this
> issue:
>
> property bool fullscreen: priv.focusedApplication ?
> priv.focusedApplication.fullscreen : false

done

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Likewise here:
>
> """
> PropertyAction { target: fadeInScreenshotImage; property: "source";
> value: ApplicationManager.findApplication(priv.newFocusedAppId).screenshot }
> """

done

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> I'm getting the following error on start up:
>
> qml/Stages/PhoneStage.qml:356: ReferenceError: shift is not defined
>
> Which is solved by:
>
> """
> SequentialAnimation {
> id: snapAnimation
> - property int targetContentX: -shift
> + property int targetContentX: -spreadView.shift
>
> UbuntuNumberAnimation {
> target: spreadView
> """

Dammit. I fixed this already and forgot to push it to this branch... Too many branches floating around right now. Thanks for pointing out!

fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Functional bugs (phone testing only)
> 1. I launch Contacts app from dash. Wait for it to appear. Left edge swipe it
> away. I'm not able to bring back Contacts by tapping its preview, nor its dahs
> or launcher icon.
>
> 2. I launch Contacts app from dash. Wait for it to appear. Left edge swipe it
> away. I then launch Dialer app from dash, I don't get a starting animation:
> instead Contacts app immediately shown, and eventually Dialer pops up on top

Both were caused because unity8 still called activateApplication() which was changed to reqeastFocusApplication() in the other branches by now.

Both fixed.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> > Functional bugs (phone testing only)
> > 1. I launch Contacts app from dash. Wait for it to appear. Left edge swipe
> it
> > away. I'm not able to bring back Contacts by tapping its preview, nor its
> dahs
> > or launcher icon.
> >
> > 2. I launch Contacts app from dash. Wait for it to appear. Left edge swipe
> it
> > away. I then launch Dialer app from dash, I don't get a starting animation:
> > instead Contacts app immediately shown, and eventually Dialer pops up on top
>
> Both were caused because unity8 still called activateApplication() which was
> changed to reqeastFocusApplication() in the other branches by now.
>
> Both fixed.

Sorry #2 was something else. I disabled one of the screenshotting workarounds in order to be faster writing the tests and forgot to reenable it back. Now both fixed for real.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> + readonly property bool applicationRunning:
> ApplicationManager.focusedApplicationId.length > 0
> I don't see this used anywhere actually.

correct. removed it

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 3. With 2 apps open, doing a full right edge swipe, the animation is linked to
> my finger distance from the right edge, until about 70% of the way, when it
> just stops. Then only finger release completes the animation. It feels like
> the animation locks up. Did Design want this?

Turns out the spread shouldn't be disabled at all if there are only 2 apps. => fixed.

>
> 4. Have app open and active. Press power to turn off display, and again to
> show greeter. Do a slow right-edge swipe - a black rectangle is revealed. Only
> on finger release does the app appear.

Fixed. good catch. The app was still suspended by ApplicationManager.suspended. I'm waking things now as soon as the greeter is moved, not only when we commit to unlock it.

>
> 5. I've 2 apps open. I open a third from the dash. Once it has appeared
> (giving it a second more just in case), I start a right edge gesture. I only
> see 2 apps in the spread, the latest one has disappeared

Caused by a dumb mistake in the latest unity-mir change. fixed, updated pacakges will show up on chinstrap soon.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 13. seems running apps icons are not all the same width:
> http://imgur.com/altNLT7 The second row doesn't match the first, column-wise.
> Note that not all apps are fullscreen any more.

Hmm... True. That's because they keep their aspect ratio now. I could wrap then in an Item to keep them aligned but that would look quite bad in the tablet scenario where portrait and landscape apps mixed in this view. Not really sure how to deal with this tbh. Any suggestions?

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Sorry about the hopeless picture, but this was something minor I noticed:
> http://imgur.com/O6EFlQh
> I opened 3 apps, dialer, contacts & system settings. With the order in the
> spread of contacts, settings, dialer, I grabbed the dialer screenshot and
> brought it as far left as I could. I noticed that a bit of the system settings
> image could be made visible over the dialer. See the image above, I marked it
> in red

fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 6. With spread open, opening launcher and tapping dash icon does nothing

fixed.

>
> 7. Have a couple of apps open, and engage the spread. Now open the (as
> example) sound indicator, and tap the "Sound settings" entry. Indicators
> close, but spread remains. Tapping the system settings does bring me to the
> correct screen.

Should be fixed now.

>
> 8. Strangely, if I open System Settings app via indicators (same steps as in 7
> above), it often opens with incorrect x position of the surface (i.e there's a
> 2-3GU gap on the left). Need to check with trunk.

hmm... quite unlikely this has anything to do with this branches. the misplaced surface is the real app surface whereas this branch only deals with screenshots which are placed correctly.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 9. Have system settings & dialer app open, dialer app to the front. Open
> indicators, select Sound and tap "Sound settings". You see the dialer app
> slide to the right, but a black rectangle remains, until suddenly system
> settings appears

This is the default app starting white rectangle which will eventually be replaced by a splash screen I believe, but a bit out of scope of this branch imo.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

The problem handles by spreadDragArea's code (collecting gesturePoints, etc) looks remarkably like what EdgeDragEvaluator does. Have you considered using it before you went with this implementation?

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

> The problem handles by spreadDragArea's code (collecting gesturePoints, etc)
> looks remarkably like what EdgeDragEvaluator does. Have you considered using
> it before you went with this implementation?

Look how DragHandle uses it.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 10. Starting Gallery app does not make panel hide (which it should)

Fixed.

>
> 11. Quick right edge swipe can be a little flickery. I'd forgive this however,
> I know what's going on.

Hmm... seems to work perfectly fine here... But I guess if you flick really quick and the device is busy otherwise you might manage to see some screenshot updating action.

>
> 12. With multiple apps open, bring up the OSK somehow. Then do right edge
> swipe to show spread. OSK stuck open

Yeah... that's a bit of an issue. Right now there's no way to hide the OSK except unfocing the app. As that causes the whole state machine to update it causes effects like the panel coming in for half a second (you you transition from fullscreen to fullscreen) and some more such issues. Changing the code to deal with that would be quite intrusive and afaik the plan is to make the OSK part of the app surface anyways. Can we ignore this for this merge?

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Question: in the spread, the edges of the transformed screenshots are not
> antialiased. Turning on smoothing bad?

I've enabled antialiasing on the tiles now. Doesn't seem to badly impact performance. Thanks for the hint. Didn't even know that property existed since QtQuick 2.0

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> 14. this is a tricky one to reproduce: open like 8+ apps. Right edge to open
> spread. Flick list to the left so you see top-most screenshot. Now flick it
> again, and while it is bouncing, do a right-edge swipe. When I do this, I
> switch to the second last app in the list (like the quick right-edge Alt-Tab
> swipe)

Ok, now that the DirectionalDragArea can handle being disabled while being used, I disabled it when entering stage 2. That prevents this from happening, however there's still a possibility to mess it a little up. If you quickly put down the Finger into the "empty" area while the Flickable overshoots and keep the finger there, the Flickable seems to get a mouseMove even, but releasing the finger now does not produce the mouseReleased event. If you now touch the flickable somewhere, it counts the distance as a movement and you can make it jump a bit before it follows the finger again. I'll try to improve it further but it doesn't seem as bad any more as before.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> nitpick: with 8+ apps open, when you flick spread to the the left, so you see
> top-most screenshot, the framerate drops a bit. Optimisation can be left for
> later naturally

The thing is, if you open 8+ apps, the overall framerate drops. Just opening 8+ apps without even using the spread (or even in trunk) makes all animations (Launcher, Dash etc) become sluggish. AlbertA and Anpok have improved this a lot already (used to happen with 4+ apps a month ago) and I believe there's still some improvements upcoming (e.g. opaque surfaces).

While there might well be room for improvements in the Spread, I think this particular issue is rather Mir related.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> > http://imgur.com/gz5p3Gg I managed to get somehow, not sure how
> Ah I had started Gmail app. Seems it took an ultra long time to start up, so
> appeared in the running apps list, but no screenshot for it existed?? So the
> delegate was too narrow?

Yeah... There is still a little issue if you minimize an app before it paints on the screen for the first time. In that case we can't grab a screenshot. Current trunk has empty tiles in here which I guess is a bit better than this. Again, the fact that we're keeping the aspect ration here doesn't ease things up. But I can try to find a workaround for the time being (until we get rid of the screenshotting stuff) if you want me to.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> > The problem handles by spreadDragArea's code (collecting gesturePoints, etc)
> > looks remarkably like what EdgeDragEvaluator does. Have you considered using
> > it before you went with this implementation?
>
> Look how DragHandle uses it.

As discussed on IRC, design wants this to complete the gesture if the user does a one-way flick but they explicitly said that the gesture speed should not have any impact on it. I'm struggling a bit to make the EdgeDragEvaluator do exactly that, so we agreed to keep it as is.

However, I'll bring up the topic of consistent edge gestures behavior (in terms of speed, distance and direction) in the next design meeting.

Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Doesn't merge with current unity8 trunk
Text conflict in plugins/Utils/plugin.cpp
Text conflict in qml/Shell.qml
2 conflicts encountered.

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

> Doesn't merge with current unity8 trunk
> Text conflict in plugins/Utils/plugin.cpp
> Text conflict in qml/Shell.qml
> 2 conflicts encountered.

fixed

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+ function hide() {
+ if (locked) {
+ return;
+ }
+ forceHide();
+ }
Why does hide not do anything if the spreadView is visible? Well more like, it sounds like an error that hide is called when spreadView is visible.

+ blockInput: ApplicationManager.focusedApplicationId.length === 0
Nitpick: checking length of the appId to be zero to prove there's no focused app? Not wrong, just reads a bit funny. Maybe String::isEmpty() works in QML, never tried it.

+ property string focusedAppId: ApplicationManager.focusedApplicationId
+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
Just checking: the extra focusedAppId property needed to properly update the focusedApplication property? (you do this in a few places, so just curious why)

+++ qml/Stages/PhoneStage.qml
+ onMovingChanged: {
...
+ } else {
+ mainScreenshotImage.visible = false;
Maybe setting mainScreenshotImage.src = "" might free a little memory when spread not open? Probably more GPU memory than system memory. It might have cost too though, so just an idea

+ Image {
+ id: mainScreenshotImage
+ property string src
+ source: src
why not just use source everywhere?

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

General JS comment: sometimes you use semicolons, other times you don't. Think it better to just use them.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ qml/Stages/PhoneStage.qml
+ onFocusedApplicationIdChanged: {
+ var application = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
Might as well use priv.focusedApplication, no?

+ id: priv
+ property string focusedAppId: ApplicationManager.focusedApplicationId
+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
Times like these I wish QML properties were readonly by default, and you had to use a specifier to specify otherwise

+ onFocusedScreenshotChanged: {
+ mainScreenshotImage.src = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId).screenshot;
mainScreenshotImage.src = focusedApplication.screenshot would do, no?

+ function requestNewScreenshot() {
+ ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
ApplicationManager.updateScreenshot(focusedAppId)

+ function indexOf(appId) {
I see why you need it, but not a giant fan of scanning the whole model for an index, just to reposition the spreadView. Not that I have a better idea however!

+ // FIXME: the signal connections seems to get lost.
+ // Check with Qt 5.2, see if we can remove this Connections and Binding objects
5.2 landed, fancy giving this a look?

+ // PropertyAction seems to fail when secondApplicationStarting and we didn't have another screenshot before
+ ScriptAction { script: mainScreenshotImage.src = priv.focusedScreenshot }
+ spreadView.snapTo(0)
Weird, I don't see why a PropertyAction would be incorrect here. Did you try setting a binding instead of a value, just as an experiment?

+ PropertyAction { target: fadeInScreenshotImage; property: "source"; value: ApplicationManager.findApplication(priv.newFocusedAppId) ? ApplicationManager.findApplication(priv.newFocusedAppId).screenshot : "" }
pity you need to search twice here, could you not store the value temporarily?

+ PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
+ PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
Fun fact: can replace this with
  PropertyAction { targets: [fadeInScreenshotImage, mainScreenshotImage]; property: "visible"; value: false }
not a demand though, both ways are fine.

+ UbuntuNumberAnimation { target: fadeInScreenshotImage; property: "opacity"; to: 1; }
+ UbuntuNumberAnimation { target: fadeInScreenshotImage; property: "scale"; to: 1; }
similarly, this can be
  UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: ["opacity", "scale"]; to: 1; }

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Questions: there are 2 appSplash instances? Is that if user starts 2 apps quickly?

+ property bool attachedToView: true
a comment explaining this would be nice. In fact, the logic in the EdgeDragArea could do with the same.

+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) && spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
please wrap that line a bit

+ property real positionMarker3: 0.6
+ property real positionMarker4: .9
Consistency please

+ // Stage of the animation:
+ property int stage: not a fan of the term, as "Stage" has a particular meaning for us. Would the Flickable's "States" system be useful here, as instead of numbers, can give the animation stages names?

+ onShiftedContentXChanged: {
+ case 1:
/me pedant, but adding "break" here too might spare a future coder some pain

+// duration: UbuntuAnimation.SleepyDuration
remove?

+ spreadView.stage = 4;
leftover?

+ if (spreadView.shiftedContentX == spreadView.width * spreadView.positionMarker2) {
what's going on here? floating point equality comparison makes me suspicious

+ id: spreadRow
+ width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
fancy maths could do with a comment explaining it :)

+++ qml/Stages/SpreadDelegate.qml
+ anchors { left: parent.left; bottom: parent.bottom; top: parent.top; topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress) }
please wrap so topMargin is on its own line, as that's the nontrivial bit.

+ scale: 1
unnecessary, that's the default value.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ qml/Stages/StageWithSideStage.qml
How closely would you like me to review this? Since there's plenty of commented out lines and stuff, would you prefer a functional & code-sanity review only?

review: Needs Information
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> + function hide() {
> + if (locked) {
> + return;
> + }
> + forceHide();
> + }
> Why does hide not do anything if the spreadView is visible? Well more like, it
> sounds like an error that hide is called when spreadView is visible.

It's needed to be able to press the home button in the launcher and return to the dash even while the spread is visible. Usually something calling hide() in the shell should not be able to hide the spread with a few exceptions. The user pressing the home button in the launcher is one of them.

>
> + blockInput: ApplicationManager.focusedApplicationId.length === 0
> Nitpick: checking length of the appId to be zero to prove there's no focused
> app? Not wrong, just reads a bit funny. Maybe String::isEmpty() works in QML,
> never tried it.

No, isEmpty() doesn't work as it's not really a QString (QString is not a QObject). It's the JavaScript string class.

>
> + property string focusedAppId: ApplicationManager.focusedApplicationId
> + property var focusedApplication:
> ApplicationManager.findApplication(focusedAppId)
> Just checking: the extra focusedAppId property needed to properly update the
> focusedApplication property? (you do this in a few places, so just curious
> why)

No... One is not to properly update the other, but rather to have everything at hands in the surrounding code without having to call functions in ApplicationManager each time and reduce length of the code.

>
> +++ qml/Stages/PhoneStage.qml
> + onMovingChanged: {
> ...
> + } else {
> + mainScreenshotImage.visible = false;
> Maybe setting mainScreenshotImage.src = "" might free a little memory when
> spread not open? Probably more GPU memory than system memory. It might have
> cost too though, so just an idea
>

That causes an issue: When you swipe the stage away with a left edge swipe and then pull it back in, this is the screenshot that is visible. If you think it's worth reworking this to unset and re-set the image for that case I'll do, but I'm not sure we should "polish" the screenshotting stuff that's supposed to go away.

> + Image {
> + id: mainScreenshotImage
> + property string src
> + source: src
> why not just use source everywhere?

There is a bug in our imageprovider. changing source does not emit sourceChanged.
https://bugs.launchpad.net/ubuntu-ui-toolkit/+bug/1194778
I've added a FIXME comment with the link to the bug report.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> General JS comment: sometimes you use semicolons, other times you don't. Think
> it better to just use them.

ack. added them.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> +++ qml/Stages/StageWithSideStage.qml
> How closely would you like me to review this? Since there's plenty of
> commented out lines and stuff, would you prefer a functional & code-sanity
> review only?

Yeah, this is really the part that's subject to be reworked when the right edge animation for the tablet comes in. So all I'd ask you to do here is to check out if its "good enough" to not totally break the tablet experience. It definitely has issues.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ qml/Stages/SwitchingApplicationImage.qml
+ onApplicationChanged: print("setting app to", application.appId, application.screenshot)
debug info necessary?

+ signal switched()
I prefer roughly sticking to the guidelines (http://qt-project.org/doc/qt-4.8/qml-coding-conventions.html) and declaring signals after properties, but before javascript function definitions

+ Image {
+ visible: true
should not be necessary

You have a couple of commented out lines in this file too, please remove them.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ qml/Stages/TransformedSpreadDelegate.qml

Since this relies heavily on the animation "stages" reported by the spreadView, a comment explaining the different behaviours of the tile in each stage (mentioning the position markers) would help a lot.

+ property real animatedProgress: 0
purpose not obvious to me how it's different to "progress", add comment please

+ property real tile1StartScale: startScale + .4
+ property real tile0SnapAngle: 10
could be made readonly, or private actually.

+ priv.stage2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
confuses me, why the -spreadView.width offset for the animation?

+ // As they are static values, lets caluclate
typo

+ property real xTranslate: {
in many of the calculations in this block, there's a "-spreadView.width" being used in the calculation of the start or end value in a linearAnimation. Could you add a note justifying it?

+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
please wrap

+ } else if(spreadView.stage == 1) {
space needed after "if"

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ tests/mocks/Unity/Application/ApplicationInfo.cpp
- ,m_state(Starting)
+ ,m_state(Running)
looks wrong to me, why would an app start immediately in a "running" state. Real AppMan does not do that.

+++ tests/mocks/Unity/Application/ApplicationInfo.h
+#include <QDebug>
not needed

+++ tests/mocks/Unity/Application/ApplicationManager.cpp
do we need the qDebugs?

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp

+ * Copyright (C) 2013 Canonical, Ltd.
2014

+ if (app == NULL) {
nullptr

+ QGuiApplication *unity = qobject_cast<QGuiApplication*>(qApp);
can go inside the else block where it's actually used. Quick comment explaining what's going on would be handy too for the causal reader (you're scaling the image the the width of the window for all main stage apps).

qDebug needed?

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

+++ tests/mocks/Unity/Application/ApplicationScreenshotProvider.h
+ * Copyright (C) 2013 Canonical, Ltd.
2014

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal
Download full text (3.5 KiB)

> +++ qml/Stages/PhoneStage.qml
> + onFocusedApplicationIdChanged: {
> + var application =
> ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
> Might as well use priv.focusedApplication, no?

yes. fixed.

>
> + id: priv
> + property string focusedAppId: ApplicationManager.focusedApplicationId
> + property var focusedApplication:
> ApplicationManager.findApplication(focusedAppId)
> + property url focusedScreenshot: focusedApplication ?
> focusedApplication.screenshot : ""
> Times like these I wish QML properties were readonly by default, and you had
> to use a specifier to specify otherwise

Hmm... due to a workaround for something I believe is a bug in Qt/QML I need to write them. Besides its not public API so I don't think its much of an issue.

>
> + onFocusedScreenshotChanged: {
> + mainScreenshotImage.src = ApplicationManager.findApplication(ApplicationMana
> ger.focusedApplicationId).screenshot;
> mainScreenshotImage.src = focusedApplication.screenshot would do, no?

yep. fixed.

>
> + function requestNewScreenshot() {
> +
> ApplicationManager.updateScreenshot(ApplicationManager.focusedApplicationId);
> ApplicationManager.updateScreenshot(focusedAppId)

fixed

> + function indexOf(appId) {
> I see why you need it, but not a giant fan of scanning the whole model for an
> index, just to reposition the spreadView. Not that I have a better idea
> however!

Hmm... This is only used in the corner case that the user launches an app using the launcher while in spread view. So not really critical performance wise. I can see why you dislike it, but as you said: not really a better idea around.

>
>
> + // FIXME: the signal connections seems to get lost.
> + // Check with Qt 5.2, see if we can remove this Connections and Binding
> objects
> 5.2 landed, fancy giving this a look?

Yep, checked. still an issue. I removed the "check with 5.2 phrase".

>
> + // PropertyAction seems to fail when secondApplicationStarting and we didn't
> have another screenshot before
> + ScriptAction { script: mainScreenshotImage.src = priv.focusedScreenshot }
> + spreadView.snapTo(0)
> Weird, I don't see why a PropertyAction would be incorrect here. Did you try
> setting a binding instead of a value, just as an experiment?

Oh yeah... this one gave me quite a headache to figure why the heck it was failing. Tried the binding as you proposed too. Doesn't work. I've added a FIXME to the comment.

>
> + PropertyAction { target: fadeInScreenshotImage; property: "source"; value:
> ApplicationManager.findApplication(priv.newFocusedAppId) ?
> ApplicationManager.findApplication(priv.newFocusedAppId).screenshot : "" }
> pity you need to search twice here, could you not store the value temporarily?

done.

>
> + PropertyAction { target: fadeInScreenshotImage; property: "visible"; value:
> false }
> + PropertyAction { target: mainScreenshotImage; property: "visible"; value:
> false }
> Fun fact: can replace this with
> PropertyAction { targets: [fadeInScreenshotImage, mainScreenshotImage];
> property: "visible"; value: false }
> not a demand though, both ways are fine.

nice one. didn't know this. Love it!

>
> + UbuntuNumberAnimation { target:...

Read more...

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Questions: there are 2 appSplash instances? Is that if user starts 2 apps
> quickly?

Yeah, it mostly catches the case that the screenshot is not available for some reason, for example the one you mentioned.

>
>
> + property bool attachedToView: true
> a comment explaining this would be nice. In fact, the logic in the
> EdgeDragArea could do with the same.

very valid. added.

>
> + if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
> spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
> please wrap that line a bit

wrapped all to 120 chars now.

>
> + property real positionMarker3: 0.6
> + property real positionMarker4: .9
> Consistency please

fixed.

>
> + // Stage of the animation:
> + property int stage: not a fan of the term, as "Stage" has a particular
> meaning for us. Would the Flickable's "States" system be useful here, as
> instead of numbers, can give the animation stages names?

Don't really want to go for strings as I do <, > comparison etc. While that would still work with strings its imo much more fail prone if someone renames or adds a stage. I did, however rename it to "phase" now to avoid the naming collision.

>
> + onShiftedContentXChanged: {
> + case 1:
> /me pedant, but adding "break" here too might spare a future coder some pain

fair enough, fixed.

>
> +// duration: UbuntuAnimation.SleepyDuration
> remove?

done.

>
> + spreadView.stage = 4;
> leftover?

seems so... dropped it.
>
> + if (spreadView.shiftedContentX == spreadView.width *
> spreadView.positionMarker2) {
> what's going on here? floating point equality comparison makes me suspicious

Turns out its not needed any more at all as I later changed the logic to always hit the branch before in this case already.

>
> + id: spreadRow
> + width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance +
> (spreadView.width - spreadView.tileDistance) * 1.5
> fancy maths could do with a comment explaining it :)

done.

> +++ qml/Stages/SpreadDelegate.qml
> + anchors { left: parent.left; bottom: parent.bottom; top: parent.top;
> topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress) }
> please wrap so topMargin is on its own line, as that's the nontrivial bit.

done.
>
> + scale: 1
> unnecessary, that's the default value.

done

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> +++ qml/Stages/SwitchingApplicationImage.qml
> + onApplicationChanged: print("setting app to", application.appId,
> application.screenshot)
> debug info necessary?
>
> + signal switched()
> I prefer roughly sticking to the guidelines (http://qt-project.org/doc/qt-4.8
> /qml-coding-conventions.html) and declaring signals after properties, but
> before javascript function definitions
>
> + Image {
> + visible: true
> should not be necessary
>
>
> You have a couple of commented out lines in this file too, please remove them.

all done. Sorry for that. Forgot to clean this one up before putting up for review.

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

<greyback> old query of mine too: with spread open, long left edge swipe has launcher appear, then disappear, and nothing else happens
<greyback> that ok?
<mzanetti> ah right... so yes, the spread should not go away, that is wanted
<mzanetti> now the question is whether the launcher should hide again (as it does normally) or not
* greyback thinks it should stay open
<mzanetti> probably, yes

Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

<greyback> mind checking this: open 1 app, left swipe it away. Then tap it's entry in running apps. The slide in animation seems wrong
<mzanetti> mhm... true... wonder when that happened. will fix

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> +++ qml/Stages/TransformedSpreadDelegate.qml
>
> Since this relies heavily on the animation "stages" reported by the
> spreadView, a comment explaining the different behaviours of the tile in each
> stage (mentioning the position markers) would help a lot.

done

>
> + property real animatedProgress: 0
> purpose not obvious to me how it's different to "progress", add comment please

done.

>
> + property real tile1StartScale: startScale + .4
> + property real tile0SnapAngle: 10
> could be made readonly, or private actually.

actually intentional in public api as I hope to be able to reuse the TransformedAppDelegate for the tablet which has a less 3D look and feel because of the bigger screen space.

>
> + priv.stage2startTranslate = priv.easingAnimation(0,
> spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4)
> + spreadView.width;
> confuses me, why the -spreadView.width offset for the animation?

tiles are attached to the right edge (outside) in the beginning and travel a distance of -spreadView.width during the progress of 0..1. So -spreadView.width is the end position. Should be more clear now that I added tons of comments all over the place.

>
> + // As they are static values, lets caluclate
> typo

fixed.

>
> + property real xTranslate: {
> in many of the calculations in this block, there's a "-spreadView.width" being
> used in the calculation of the start or end value in a linearAnimation. Could
> you add a note justifying it?

as explained above.

>
> + return linearAnimation(selectedProgress, negativeProgress,
> selectedXTranslate, selectedXTranslate - spreadView.tileDistance,
> root.progress);
> please wrap

done

>
> + } else if(spreadView.stage == 1) {
> space needed after "if"

done

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> +++ tests/mocks/Unity/Application/ApplicationInfo.cpp
> - ,m_state(Starting)
> + ,m_state(Running)
> looks wrong to me, why would an app start immediately in a "running" state.
> Real AppMan does not do that.

Ok. changed to start 'em up "Starting" and added a timer that switches them to "Running" after 300ms.

>
> +++ tests/mocks/Unity/Application/ApplicationInfo.h
> +#include <QDebug>
> not needed

gone

>
> +++ tests/mocks/Unity/Application/ApplicationManager.cpp
> do we need the qDebugs?

gone

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> +++ tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp
>
> + * Copyright (C) 2013 Canonical, Ltd.
> 2014

fixed.

>
> + if (app == NULL) {
> nullptr

fixed.

>
> + QGuiApplication *unity = qobject_cast<QGuiApplication*>(qApp);
> can go inside the else block where it's actually used. Quick comment
> explaining what's going on would be handy too for the causal reader (you're
> scaling the image the the width of the window for all main stage apps).

fixed.

>
> qDebug needed?

removed most debugs. changed one to be a qWarning in case we can't find an image for the given app.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> <greyback> old query of mine too: with spread open, long left edge swipe has
> launcher appear, then disappear, and nothing else happens
> <greyback> that ok?
> <mzanetti> ah right... so yes, the spread should not go away, that is wanted
> <mzanetti> now the question is whether the launcher should hide again (as it
> does normally) or not
> * greyback thinks it should stay open
> <mzanetti> probably, yes

fixed. actually it even got me around the hide() vs forceHide() you disliked in an earlier comment

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Tablet testing issues [MS=main stage, SS=side stage]
1) opening just 1 SS app, unable to swipe it away with SS handle
2) open some apps, then close them all, "Running apps" section of apps lens does not disappear like it used to
3) with 1 SS app open, unable to interact with Dash (missing InputFilterArea perhaps)
4) with only SS app open, open MS app (via launcher) - MS starting animation incorrect
5) with only SS app open, open MS app (via launcher) - SS dismissed
6) dismissed SS keeps a shadow on the right of the screen
7) 1 MS & 1 SS app open. Return to dash. Tap SS recent app - only SS app appears. Used to be that both MS & SS app appeared.
8) App lens - MS app preview much wider than it used to be, it used to be intentionally cropped
9) not sure why, but SS app preview image in the apps lens sometimes incorrect, sometimes stretched, sometimes offset wrong and being tiled inside the preview, and sometimes the wrong image entirely. Possibly a Nexus10 bug, just strange as the window animation screenshots seem to always look fine
10) 2 MS apps open: Clock & Browser, Browser focused. Return to dash and tap the Clock entry. The animation to bring Clock to the front isn't right, looks like mix of spread animation with usual slide in animation.
11) no right edge gesture at all now (for switching apps, think it was SS app switching)?

review: Needs Fixing (functional: tablet)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Phone functional testing pass 2 issues
1) Open 2 apps, return to dash. Tap preview on one app - the "bring to front" animation looks wrong
2) open 2 apps, go to spread. Open launcher and tap icon of focused app - nothing happens. Tapping icon of other app focuses it.
3)

>
> 12. With multiple apps open, bring up the OSK somehow. Then do right edge
> swipe to show spread. OSK stuck open

Yeah... that's a bit of an issue. Right now there's no way to hide the OSK except unfocing the app. ... Can we ignore this for this merge?
Ok, I can live with that.

review: Needs Fixing (functional: phone)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> <greyback> mind checking this: open 1 app, left swipe it away. Then tap
> it's entry in running apps. The slide in animation seems wrong
> <mzanetti> mhm... true... wonder when that happened. will fix

fixed.

> + Image {
> + id: mainScreenshotImage
> + property string src
> + source: src
> why not just use source everywhere?

along the way I got rid of this too, which also get away with this

+ // PropertyAction seems to fail when secondApplicationStarting and we didn't have another screenshot before

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Phone functional testing pass 2 issues
> 1) Open 2 apps, return to dash. Tap preview on one app - the "bring to front"
> animation looks wrong

fixed

> 2) open 2 apps, go to spread. Open launcher and tap icon of focused app -
> nothing happens. Tapping icon of other app focuses it.
> 3)

fixed.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> Tablet testing issues [MS=main stage, SS=side stage]
> 1) opening just 1 SS app, unable to swipe it away with SS handle

fixed.

> 2) open some apps, then close them all, "Running apps" section of apps lens
> does not disappear like it used to

fixed.

> 3) with 1 SS app open, unable to interact with Dash (missing InputFilterArea
> perhaps)

fixed.

> 4) with only SS app open, open MS app (via launcher) - MS starting animation
> incorrect

Hmm... I did change something in this regard but not sure if this is h

> 5) with only SS app open, open MS app (via launcher) - SS dismissed

Uhm... ok. reverted the "fix" :)

> 6) dismissed SS keeps a shadow on the right of the screen

fixed.

> 7) 1 MS & 1 SS app open. Return to dash. Tap SS recent app - only SS app
> appears. Used to be that both MS & SS app appeared.

Yeah, but this time I really believe trunk's behavior is the wrong one :D

> 8) App lens - MS app preview much wider than it used to be, it used to be
> intentionally cropped

fixed.

> 9) not sure why, but SS app preview image in the apps lens sometimes
> incorrect, sometimes stretched, sometimes offset wrong and being tiled inside
> the preview, and sometimes the wrong image entirely. Possibly a Nexus10 bug,
> just strange as the window animation screenshots seem to always look fine

uhm... happens very rarely - I've seen it too today. Can we let this go and focus on eliminating the screenshots?

> 10) 2 MS apps open: Clock & Browser, Browser focused. Return to dash and tap
> the Clock entry. The animation to bring Clock to the front isn't right, looks
> like mix of spread animation with usual slide in animation.

Hmm.. not sure if I fixed this with other changed, but looks correct here.

> 11) no right edge gesture at all now (for switching apps, think it was SS app
> switching)?

I hoped to get a bypass ticket on that one instead of juggling screenshots around just to remove them with my next task again. But if it turns out this is too much breakage, I'll obviously do my best to fix this too.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Running AP tests, here are my results: http://studio.sketchpad.cc/Cggnfp2Eiq
Unity8 fails some tests

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Needs merging with trunk
  Text conflict in qml/Dash/DashApps.qml
  Contents conflict in qml/Dash/DashPreview.qml
  Text conflict in qml/Dash/GenericScopeView.qml
  Text conflict in qml/Shell.qml
  Text conflict in tests/autopilot/unity8/shell/emulators/dash.py

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
Mirco Müller (macslow) wrote : Posted in a previous version of this proposal

I ran ppa:ci-train-ppa-service/landing-015 with r261 on my N4 through https://wiki.ubuntu.com/Process/Merges/TestPlans/Unity8 and it held up pretty good. Especially the new right-edge feels robust and slick. But I came across this issue with starting the Notes app...

http://ubuntuone.com/6TmrkBPbLkqPiVdhkXalvQ
http://ubuntuone.com/1zSsYq6czLOiPI58Y6yGeL
http://ubuntuone.com/2DwRiTX61Pbmrosd0xlA3p
http://ubuntuone.com/4SKadILKJ6RqirwYqreCsn

Before that I had up to ten applications running (among them camera-app and browser) and there was no single glitch. I'm still trying to find a way to trigger this bug in a reproducable manner.

review: Needs Fixing
Revision history for this message
Mirco Müller (macslow) wrote : Posted in a previous version of this proposal

Here's a screencast better showing off the "invisible" apps and the empty app-previews: https://www.youtube.com/watch?v=C66Y0vNSboI

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> I ran ppa:ci-train-ppa-service/landing-015 with r261 on my N4 through
> https://wiki.ubuntu.com/Process/Merges/TestPlans/Unity8 and it held up pretty
> good. Especially the new right-edge feels robust and slick. But I came across
> this issue with starting the Notes app...
>
> http://ubuntuone.com/6TmrkBPbLkqPiVdhkXalvQ
> http://ubuntuone.com/1zSsYq6czLOiPI58Y6yGeL
> http://ubuntuone.com/2DwRiTX61Pbmrosd0xlA3p
> http://ubuntuone.com/4SKadILKJ6RqirwYqreCsn
>
> Before that I had up to ten applications running (among them camera-app and
> browser) and there was no single glitch. I'm still trying to find a way to
> trigger this bug in a reproducable manner.

The misplaced surface is fixed in another branch in unity-mir. I've added it to the silo. Upgrading it should get it fixed. The missing image however, happens if an app fails to start. Its the same behavior as current trunk.

Revision history for this message
Michael Zanetti (mzanetti) wrote : Posted in a previous version of this proposal

> The missing image however,
> happens if an app fails to start. Its the same behavior as current trunk.

Some more details: This is in unity-mir and possibly even lower layers. The situation will improve once we have the live surfaces (it will fix the case where the screenshot is missing if you swipe it away before its loaded), and for apps not starting at all, we need to figure a solution in ApplicationManager. I don't adding a workaround in here is the way to go.

Revision history for this message
Mirco Müller (macslow) wrote : Posted in a previous version of this proposal

After the second run through the test-plan with the reconfigure-fixes in the PPA, I'm ok with the results. All works nicely. Good to go.

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

Code-wise I'm happy to go too. I did a quick test of phone and it was good too. Not done a last tablet check however

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote : Posted in a previous version of this proposal

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Hell yeah
 * Did CI run pass? If not, please explain why.
Dependant branches, listed in description

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

Approving as per the superseded MP. This only brought a trunk merge.

Revision history for this message
Michał Sawicz (saviq) wrote :

To fix:
- I got "reliable" autopilot failures on mako
- sidestage enabled on flo (shoould be >= 100gu)
- sidestage input on flo is misplaced (could be the same as above)
- we should clean up everything surfaceflinger-related (unity-mir qml installation dir, unity8 mangling import path, support for .display-mir etc.).

review: Needs Fixing
Revision history for this message
Michał Sawicz (saviq) wrote :

Files to clean up:

lxc-android-config: /etc/init/boot-hooks/set-display-mir.conf
ubuntu-touch-session: /etc/profile.d/qpa_plugin.sh
/etc/environment → lool/ogra/rsalveti should know where that comes from

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

One more:
ubuntu-touch-session: /usr/share/ubuntu-touch-session/usc-wrapper

lp:~unity-team/unity8/right-edge-2 updated
738. By Michael Zanetti

make autopilot work *again*

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Leo Arias (elopio) wrote :

2972 -class DashApps(GenericScopeView):
2973 - """Autopilot emulator for the applications scope."""
2974 -
2975 +class DashApps(emulators.UnityEmulatorBase):

I don't get why you changed the base class. You are removing the open_scope method from DashApps, and duplicating _get_category_element.

3009 - self.assertIsInstance(scope, dash_emulators.DashApps)

This check was important ^. If open_scope no longer returns a DashApps, I might need to make some changes on the unity-scope-click tests. Can you please ping me on IRC?

review: Needs Information (autopilot code review)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> 2972 -class DashApps(GenericScopeView):
> 2973 - """Autopilot emulator for the applications scope."""
> 2974 -
> 2975 +class DashApps(emulators.UnityEmulatorBase):
>
> I don't get why you changed the base class. You are removing the open_scope
> method from DashApps, and duplicating _get_category_element.

hmm right. Because I dropped it first as it was optimized to be a GenericScopeView, then I merged trunk which apparently prevents optimization again so I had to put it back in.

>
> 3009 - self.assertIsInstance(scope, dash_emulators.DashApps)
>
> This check was important ^. If open_scope no longer returns a DashApps, I
> might need to make some changes on the unity-scope-click tests.

Well, it is again. but its annoying to update this with every commit over and over again.

lp:~unity-team/unity8/right-edge-2 updated
739. By Michael Zanetti

revert some changes in AP which are not required any more

740. By Michael Zanetti

merge

741. By Michael Zanetti

bring back some comment

742. By Michael Zanetti

oops. wrong one :D

Revision history for this message
Michael Zanetti (mzanetti) wrote :

ok. so I've reverted the autopilot changes again as DashApps decided to indeed be a DashApps in current configuration, but I think we should get this more robust against QML optimizations.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~unity-team/unity8/right-edge-2 updated
743. By Michael Zanetti

move sidestage out of the way when the focused app changes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~unity-team/unity8/right-edge-2 updated
744. By Michael Zanetti

pull in application-impl-2

745. By Michael Zanetti

also depend on application-impl (without specific version)

746. By Michael Zanetti

add missing comma

747. By Michael Zanetti

unity-private doesn't provide Unity.Application... it's libunity-mir1!

748. By Michael Zanetti

drop pulling in qtubuntu for Unity.Application. It doesn't provide it any more

749. By Michał Sawicz

Bump version to ensure incompatibility with previous Unity.Application implementations.

750. By Michael Zanetti

drop the apps killing workaround again that came in handy while developing

751. By Michael Zanetti

clean out libuntuy-mir1. It'll be pulled by shlibs now

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~unity-team/unity8/right-edge-2 updated
752. By Michał Sawicz

Fix indentation and drop unity8-private providing unity-application-2, since it doesn't.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
lp:~unity-team/unity8/right-edge-2 updated
753. By Michael Zanetti

don't immediately focus a newly started app

754. By Michael Zanetti

merge

755. By Michael Zanetti

drop newline

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Yeaee.

review: Approve
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> Yeaee.

/me goes for a beer :D

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2014-03-27 12:38:21 +0000
3+++ debian/changelog 2014-04-01 18:39:46 +0000
4@@ -1,3 +1,10 @@
5+unity8 (7.85-0ubuntu1) UNRELEASED; urgency=medium
6+
7+ * Bump version to ensure incompatibility with previous Unity.Application
8+ implementations.
9+
10+ -- Michał Sawicz <michal.sawicz@canonical.com> Mon, 31 Mar 2014 14:44:30 +0100
11+
12 unity8 (7.84+14.04.20140327.1-0ubuntu1) trusty; urgency=low
13
14 [ Michał Sawicz ]
15
16=== modified file 'debian/control'
17--- debian/control 2014-03-24 11:49:06 +0000
18+++ debian/control 2014-04-01 18:39:46 +0000
19@@ -17,7 +17,7 @@
20 libnih-dev,
21 libpulse-dev,
22 libqmenumodel-dev (>= 0.2.7),
23- libunity-api-dev (>= 7.80.4),
24+ libunity-api-dev (>= 7.80.6),
25 libunity-mir-dev,
26 libupstart-dev,
27 libusermetricsoutput1-dev,
28@@ -83,9 +83,10 @@
29 qtdeclarative5-ubuntu-ui-toolkit-plugin,
30 qtdeclarative5-unity-notifications-plugin | unity-notifications-impl,
31 qtdeclarative5-xmllistmodel-plugin,
32+ unity-application-impl-2,
33 unity-launcher-impl-3,
34 unity-notifications-impl-2,
35- unity8-fake-env | qtubuntu-shell,
36+ unity8-fake-env | unity-application-impl,
37 unity8-private (= ${binary:Version}),
38 unity8-private | unity-launcher-impl,
39 unity-plugin-scopes | unity-scopes-impl,
40@@ -97,7 +98,8 @@
41 Recommends: unity-scope-click,
42 unity-scope-mediascanner2,
43 unity-scope-scopes,
44-Breaks: indicator-network (<< 0.5.1)
45+Breaks: indicator-network (<< 0.5.1),
46+ ubuntu-touch-session (<< 0.107),
47 Replaces: ubuntu-touch-session (<< 0.82~)
48 Description: Unity 8 shell
49 The Unity 8 shell is the primary user interface for Ubuntu devices.
50@@ -134,7 +136,8 @@
51 Pre-Depends: ${misc:Pre-Depends},
52 Depends: ${misc:Depends},
53 ${shlibs:Depends},
54-Provides: qtubuntu-shell,
55+Provides: unity-application-impl,
56+ unity-application-impl-2,
57 Description: Fake environment for running Unity 8 shell
58 Provides fake implementations of some QML modules used by Unity 8 shell
59 (e.g Ubuntu.Application) so that you can run it in a sandboxed environment.
60
61=== modified file 'debian/unity8.install'
62--- debian/unity8.install 2014-03-05 12:52:57 +0000
63+++ debian/unity8.install 2014-04-01 18:39:46 +0000
64@@ -10,6 +10,6 @@
65 usr/share/unity8/Notifications
66 usr/share/unity8/Panel
67 usr/share/unity8/Shell.qml
68-usr/share/unity8/SideStage
69+usr/share/unity8/Stages
70 usr/share/unity8/graphics
71 data/unity8.conf usr/share/upstart/sessions/
72
73=== modified file 'plugins/Utils/CMakeLists.txt'
74--- plugins/Utils/CMakeLists.txt 2014-03-17 15:50:50 +0000
75+++ plugins/Utils/CMakeLists.txt 2014-04-01 18:39:46 +0000
76@@ -18,6 +18,7 @@
77 qsortfilterproxymodelqml.cpp
78 timeformatter.cpp
79 unitymenumodelpaths.cpp
80+ easingcurve.cpp
81 plugin.cpp
82 )
83
84@@ -33,7 +34,7 @@
85 # files directly in targets.
86 set_target_properties(Utils-qml PROPERTIES COMPILE_FLAGS -fvisibility=default)
87
88-qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Concurrent)
89+qt5_use_modules(Utils-qml Qml Quick DBus Network XmlPatterns Gui Concurrent)
90
91 # export the qmldir qmltypes and plugin files
92 export_qmlfiles(Utils Utils)
93
94=== added file 'plugins/Utils/easingcurve.cpp'
95--- plugins/Utils/easingcurve.cpp 1970-01-01 00:00:00 +0000
96+++ plugins/Utils/easingcurve.cpp 2014-04-01 18:39:46 +0000
97@@ -0,0 +1,67 @@
98+/*
99+ * Copyright 2014 Canonical Ltd.
100+ *
101+ * This program is free software; you can redistribute it and/or modify
102+ * it under the terms of the GNU Lesser General Public License as published by
103+ * the Free Software Foundation; version 3.
104+ *
105+ * This program is distributed in the hope that it will be useful,
106+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
107+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
108+ * GNU Lesser General Public License for more details.
109+ *
110+ * You should have received a copy of the GNU Lesser General Public License
111+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
112+ *
113+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
114+*/
115+
116+#include "easingcurve.h"
117+
118+
119+EasingCurve::EasingCurve(QObject *parent):
120+ QObject(parent)
121+{
122+
123+}
124+
125+QEasingCurve::Type EasingCurve::type() const
126+{
127+ return m_easingCurve.type();
128+}
129+
130+void EasingCurve::setType(const QEasingCurve::Type &type)
131+{
132+ m_easingCurve.setType(type);
133+ Q_EMIT typeChanged();
134+}
135+
136+qreal EasingCurve::period() const
137+{
138+ return m_easingCurve.period();
139+}
140+
141+void EasingCurve::setPeriod(qreal period)
142+{
143+ m_easingCurve.setPeriod(period);
144+ Q_EMIT periodChanged();
145+}
146+
147+qreal EasingCurve::progress() const
148+{
149+ return m_progress;
150+}
151+
152+void EasingCurve::setProgress(qreal progress)
153+{
154+ if (m_progress != progress) {
155+ m_progress = progress;
156+ m_value = m_easingCurve.valueForProgress(m_progress);
157+ Q_EMIT progressChanged();
158+ }
159+}
160+
161+qreal EasingCurve::value() const
162+{
163+ return m_value;
164+}
165
166=== added file 'plugins/Utils/easingcurve.h'
167--- plugins/Utils/easingcurve.h 1970-01-01 00:00:00 +0000
168+++ plugins/Utils/easingcurve.h 2014-04-01 18:39:46 +0000
169@@ -0,0 +1,70 @@
170+/*
171+ * Copyright 2014 Canonical Ltd.
172+ *
173+ * This program is free software; you can redistribute it and/or modify
174+ * it under the terms of the GNU Lesser General Public License as published by
175+ * the Free Software Foundation; version 3.
176+ *
177+ * This program is distributed in the hope that it will be useful,
178+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
179+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
180+ * GNU Lesser General Public License for more details.
181+ *
182+ * You should have received a copy of the GNU Lesser General Public License
183+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
184+ *
185+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
186+*/
187+
188+#ifndef EASINGCURVE_H
189+#define EASINGCURVE_H
190+
191+#include <QObject>
192+#include <QEasingCurve>
193+
194+/**
195+ * @brief The EasingCurve class
196+ *
197+ * This class exposes the QEasingCurve C++ API to QML.
198+ * This is useful for user interactive animations. While the QML Animation types
199+ * all require a "from", "to" and "duration", this one is based on "period" and
200+ * "progress". So you can control the position of the aimation by changing the
201+ * progress, also going back and forward in the animation. Depending on the type
202+ * of the easing curve, value will return the transformed progress.
203+ */
204+
205+class EasingCurve: public QObject
206+{
207+ Q_OBJECT
208+ Q_ENUMS(QEasingCurve::Type)
209+ Q_PROPERTY(QEasingCurve::Type type READ type WRITE setType NOTIFY typeChanged)
210+ Q_PROPERTY(qreal period READ period WRITE setPeriod NOTIFY periodChanged)
211+ Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
212+ Q_PROPERTY(qreal value READ value NOTIFY progressChanged)
213+
214+public:
215+ EasingCurve(QObject *parent = 0);
216+
217+ QEasingCurve::Type type() const;
218+ void setType(const QEasingCurve::Type &type);
219+
220+ qreal period() const;
221+ void setPeriod(qreal period);
222+
223+ qreal progress() const;
224+ void setProgress(qreal progress);
225+
226+ qreal value() const;
227+
228+Q_SIGNALS:
229+ void typeChanged();
230+ void periodChanged();
231+ void progressChanged();
232+
233+private:
234+ QEasingCurve m_easingCurve;
235+ qreal m_progress;
236+ qreal m_value;
237+};
238+
239+#endif
240
241=== modified file 'plugins/Utils/plugin.cpp'
242--- plugins/Utils/plugin.cpp 2014-03-17 15:50:50 +0000
243+++ plugins/Utils/plugin.cpp 2014-04-01 18:39:46 +0000
244@@ -32,6 +32,7 @@
245 #include "qsortfilterproxymodelqml.h"
246 #include "timeformatter.h"
247 #include "unitymenumodelpaths.h"
248+#include "easingcurve.h"
249
250 static const char* BOTTOM_BAR_VISIBILITY_COMMUNICATOR_DBUS_PATH = "/BottomBarVisibilityCommunicator";
251 static const char* DBUS_SERVICE = "com.canonical.Shell.BottomBarVisibilityCommunicator";
252@@ -46,6 +47,7 @@
253 qmlRegisterType<TimeFormatter>(uri, 0, 1, "TimeFormatter");
254 qmlRegisterType<GDateTimeFormatter>(uri, 0, 1, "GDateTimeFormatter");
255 qmlRegisterUncreatableType<BottomBarVisibilityCommunicatorShell>(uri, 0, 1, "BottomBarVisibilityCommunicatorShell", "Can't create BottomBarVisibilityCommunicatorShell");
256+ qmlRegisterType<EasingCurve>(uri, 0, 1, "EasingCurve");
257 }
258
259 void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
260
261=== modified file 'qml/CMakeLists.txt'
262--- qml/CMakeLists.txt 2013-12-17 16:04:47 +0000
263+++ qml/CMakeLists.txt 2014-04-01 18:39:46 +0000
264@@ -13,7 +13,7 @@
265 Hud
266 Panel
267 Launcher
268- SideStage
269+ Stages
270 Notifications
271 )
272
273
274=== removed file 'qml/Components/ApplicationManagerWrapper.qml'
275--- qml/Components/ApplicationManagerWrapper.qml 2014-03-05 12:52:57 +0000
276+++ qml/Components/ApplicationManagerWrapper.qml 1970-01-01 00:00:00 +0000
277@@ -1,169 +0,0 @@
278-/*
279- * Copyright (C) 2013 Canonical, Ltd.
280- *
281- * This program is free software; you can redistribute it and/or modify
282- * it under the terms of the GNU General Public License as published by
283- * the Free Software Foundation; version 3.
284- *
285- * This program is distributed in the hope that it will be useful,
286- * but WITHOUT ANY WARRANTY; without even the implied warranty of
287- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
288- * GNU General Public License for more details.
289- *
290- * You should have received a copy of the GNU General Public License
291- * along with this program. If not, see <http://www.gnu.org/licenses/>.
292- */
293-
294-import QtQuick 2.0
295-import Unity.Application 0.1
296-
297-Item {
298- id: root
299-
300- property alias mainStageApplications: mainStageModel
301- property alias sideStageApplications: sideStageModel
302- property variant mainStageFocusedApplication: null
303- property variant sideStageFocusedApplication: null
304- property bool sideStageEnabled: true
305- property bool keyboardVisible: ApplicationManager.keyboardVisible
306- property int keyboardHeight: ApplicationManager.keyboardHeight
307-
308- property bool fake: ApplicationManager.fake ? ApplicationManager.fake : false
309-
310- signal focusRequested(string appId)
311-
312- ApplicationsModelStageFiltered {
313- id: mainStageModel
314- stage: ApplicationInfo.MainStage
315- }
316-
317- ApplicationsModelStageFiltered {
318- id: sideStageModel
319- stage: ApplicationInfo.SideStage
320- }
321-
322- Connections {
323- target: ApplicationManager
324- onFocusedApplicationIdChanged: {
325- var app = ApplicationManager.findApplication(ApplicationManager.focusedApplicationId)
326- if (!app) { //nothing at all focused, so clear all
327- mainStageFocusedApplication = null;
328- sideStageFocusedApplication = null;
329- } else {
330- if (app.stage == ApplicationInfo.MainStage) {
331- mainStageFocusedApplication = app;
332- // possible the side stage app being unfocused fired this signal, so check for it
333- if (sideStageFocusedApplication && !sideStageFocusedApplication.focused)
334- sideStageFocusedApplication = null;
335- } else {
336- sideStageFocusedApplication = app;
337- // possible the main stage app being unfocused fired this signal, so check for it
338- if (mainStageFocusedApplication && !mainStageFocusedApplication.focused)
339- mainStageFocusedApplication = null;
340- }
341- }
342- }
343-
344- onFocusRequested: {
345- // if no side stage enabled, override application's stage parameter
346- var app = ApplicationManager.findApplication(appId);
347- if (app && app.stage === ApplicationInfo.SideStage && !sideStageEnabled) {
348- app.stage = ApplicationInfo.MainStage;
349- }
350-
351- root.focusRequested(appId);
352- }
353- }
354-
355- function activateApplication(desktopFile, argument) {
356- var appId;
357-
358- // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
359- // TODO: convert entire shell to use appId only.
360- if (desktopFile.indexOf(".desktop") >= 0) {
361- appId = desktopFileToAppId(desktopFile);
362- } else {
363- appId = desktopFile;
364- }
365-
366- var application = ApplicationManager.findApplication(appId);
367- if (application !== null) {
368- return application;
369- }
370-
371- var execFlags = sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
372-
373- if (argument) {
374- return ApplicationManager.startApplication(appId, execFlags, [argument]);
375- } else {
376- return ApplicationManager.startApplication(appId, execFlags);
377- }
378- }
379-
380- function stopApplication(application) {
381- var appId;
382-
383- // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
384- // TODO: convert entire shell to use appId only.
385- if (typeof application == "string") {
386- appId = application;
387- } else {
388- appId = desktopFileToAppId(application.desktopFile);
389- }
390-
391- ApplicationManager.stopApplication(appId);
392- }
393-
394- function focusApplication(application) {
395- if (application == null || application == undefined) {
396- return;
397- }
398-
399- ApplicationManager.focusApplication(application.appId);
400- }
401-
402- function unfocusCurrentApplication() {
403- ApplicationManager.unfocusCurrentApplication();
404- }
405-
406- function moveRunningApplicationStackPosition(from, to, stage) {
407- if (from == to || from < 0 || to < 0) return;
408-
409- if (stage == ApplicationInfo.SideStage) {
410- sideStageModel.move(from, to);
411- } else {
412- mainStageModel.move(from, to);
413- }
414- }
415-
416- function getApplicationFromDesktopFile(desktopFile, stage) {
417- var appId;
418-
419- // HACK: Applications identified sometimes with with appId, but mostly with desktopFile.
420- // TODO: convert entire shell to use appId only.
421- if (desktopFile.indexOf(".desktop") >= 0) {
422- appId = desktopFileToAppId(desktopFile);
423- } else {
424- appId = desktopFile;
425- }
426-
427- for (var i = 0, len = ApplicationManager.count; i < len; i++ ) {
428- var app = ApplicationManager.get(i);
429-
430- // if stage not specified, return whichever app running on either stage
431- if (app.appId == appId && (stage == undefined || app.stage == stage)) {
432- return app;
433- }
434- }
435- }
436-
437- function desktopFileToAppId(desktopFile) {
438- var right = desktopFile.lastIndexOf(".desktop");
439- var left = desktopFile.lastIndexOf("/");
440- if (left == -1 || right == -1 || left == right) {
441- console.log("ApplicationManagerWrapper: unable to extract appId from '" + desktopFile + "'");
442- return "";
443- }
444- return desktopFile.substring(left+1, right);
445- }
446-}
447
448=== removed file 'qml/Components/ApplicationScreenshot.qml'
449--- qml/Components/ApplicationScreenshot.qml 2014-03-05 12:52:57 +0000
450+++ qml/Components/ApplicationScreenshot.qml 1970-01-01 00:00:00 +0000
451@@ -1,61 +0,0 @@
452-/*
453- * Copyright (C) 2013 Canonical, Ltd.
454- *
455- * This program is free software; you can redistribute it and/or modify
456- * it under the terms of the GNU General Public License as published by
457- * the Free Software Foundation; version 3.
458- *
459- * This program is distributed in the hope that it will be useful,
460- * but WITHOUT ANY WARRANTY; without even the implied warranty of
461- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
462- * GNU General Public License for more details.
463- *
464- * You should have received a copy of the GNU General Public License
465- * along with this program. If not, see <http://www.gnu.org/licenses/>.
466- */
467-
468-import QtQuick 2.0
469-import Ubuntu.Components 0.1
470-import Unity.Application 0.1
471-
472-Item {
473- id: applicationScreenshot
474-
475- property var application: null
476- property bool withBackground: false
477- property bool ready: applicationImage.ready || withBackground
478-
479- function setApplication(application) {
480- applicationScreenshot.application = application
481- }
482-
483- function clearApplication() {
484- applicationScreenshot.withBackground = false;
485- applicationScreenshot.application = null
486- applicationScreenshot.scheduleUpdate();
487- }
488-
489- function scheduleUpdate() {
490- applicationImage.scheduleUpdate()
491- }
492-
493- function updateFromCache() {
494- applicationImage.updateFromCache()
495- }
496-
497- Rectangle {
498- id: background
499- anchors.fill: parent
500- color: "white" // FIXME should use normal background color of Suru theme
501- visible: applicationScreenshot.withBackground
502- }
503-
504- ApplicationImage {
505- id: applicationImage
506- objectName: "screenshot image"
507- width: applicationScreenshot.application ? parent.width : 0
508- height: applicationScreenshot.application ? parent.height : 0
509- visible: applicationScreenshot.application != null && ready
510- source: ApplicationManager.findApplication((application) ? application.appId : "")
511- }
512-}
513
514=== removed file 'qml/Components/ApplicationsModelStageFiltered.qml'
515--- qml/Components/ApplicationsModelStageFiltered.qml 2013-09-09 10:20:21 +0000
516+++ qml/Components/ApplicationsModelStageFiltered.qml 1970-01-01 00:00:00 +0000
517@@ -1,49 +0,0 @@
518-/*
519- * Copyright (C) 2013 Canonical, Ltd.
520- *
521- * This program is free software; you can redistribute it and/or modify
522- * it under the terms of the GNU General Public License as published by
523- * the Free Software Foundation; version 3.
524- *
525- * This program is distributed in the hope that it will be useful,
526- * but WITHOUT ANY WARRANTY; without even the implied warranty of
527- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
528- * GNU General Public License for more details.
529- *
530- * You should have received a copy of the GNU General Public License
531- * along with this program. If not, see <http://www.gnu.org/licenses/>.
532- */
533-
534-import QtQuick 2.0
535-import Utils 0.1
536-import Unity.Application 0.1
537-
538-SortFilterProxyModel {
539- id: root
540- property int stage
541-
542- function get(index) {
543- return model.get(mapRowToSource(index));
544- }
545-
546- function move(from, to) {
547- var realFrom = mapRowToSource(from);
548- if (realFrom == -1) {
549- console.log("ERROR; invalid from=" + from + " index in move operation");
550- return;
551- }
552- var realTo = mapRowToSource(to);
553- if (realTo == -1) {
554- // assuming wanting to move to end of list
555- realTo = model.count - 1;
556- }
557-
558- if (realFrom == realTo) return;
559- model.move(realFrom, realTo);
560- }
561-
562- model: ApplicationManager
563- dynamicSortFilter: true
564- filterRole: ApplicationManager.RoleStage
565- filterRegExp: RegExp(stage)
566-}
567
568=== modified file 'qml/Components/ResponsiveFlowView.qml'
569--- qml/Components/ResponsiveFlowView.qml 2013-06-05 22:03:08 +0000
570+++ qml/Components/ResponsiveFlowView.qml 2014-04-01 18:39:46 +0000
571@@ -27,9 +27,8 @@
572 property alias verticalSpacing: flow.verticalSpacing
573 property alias horizontalSpacing: flow.horizontalSpacing
574 property int referenceDelegateWidth
575- property alias firstModel: repeater1.model
576- property alias secondModel: repeater2.model
577- property alias delegate: repeater1.delegate
578+ property alias model: repeater.model
579+ property alias delegate: repeater.delegate
580 readonly property int cellWidth: referenceDelegateWidth + horizontalSpacing
581 readonly property int cellHeight: referenceDelegateWidth + verticalSpacing
582 property alias move: flow.move
583@@ -69,13 +68,7 @@
584 property int margin: allocatableVerticalSpace - columns * horizontalSpacing
585
586 Repeater {
587- id: repeater1
588- model: (root.model) ? root.model[0] : null
589- }
590- Repeater {
591- id: repeater2
592- model: (root.model) ? root.model[1] : null
593- delegate: repeater1.delegate
594+ id: repeater
595 }
596 }
597 }
598
599=== modified file 'qml/Dash/Apps/RunningApplicationTile.qml'
600--- qml/Dash/Apps/RunningApplicationTile.qml 2014-03-12 10:44:18 +0000
601+++ qml/Dash/Apps/RunningApplicationTile.qml 2014-04-01 18:39:46 +0000
602@@ -23,7 +23,6 @@
603 AbstractButton {
604 id: root
605 property var application
606- property bool __sideStageEnabled: shell.applicationManager.sideStageEnabled
607
608 signal requestedApplicationActivation(var application)
609 signal requestedApplicationTermination(var application)
610@@ -34,7 +33,7 @@
611 // To avoid "binding loop" warning
612 height: shapedApplicationImage.height + labelContainer.height
613
614- width: shapedApplicationImage.width
615+ width: shapedApplicationImage.width <= units.gu(11) ? units.gu(11) : height
616
617 property bool terminationModeEnabled: false
618
619@@ -51,32 +50,23 @@
620 }
621 }
622
623- function updateScreenshotFromCache() {
624- applicationImage.updateFromCache();
625- }
626-
627- // FIXME: should use UbuntuShape from SDK
628- UbuntuShapeForItem {
629+ UbuntuShape {
630 id: shapedApplicationImage
631- anchors {
632- top: parent.top
633- horizontalCenter: parent.horizontalCenter
634+ anchors { top: parent.top; horizontalCenter: parent.horizontalCenter }
635+
636+ height: units.gu(17)
637+ width: applicationImage.width
638+ radius: "medium"
639+
640+ image: Image {
641+ id: applicationImage
642+ source: application.screenshot
643+ // height : width = ss.height : ss.width
644+ height: shapedApplicationImage.height
645+ fillMode: Image.PreserveAspectCrop
646+ width: Math.min(height, height * sourceSize.width / sourceSize.height)
647 }
648
649- // FIXME: width and height should be defined according to the
650- // application window's aspect ratio.
651- width: (application.stage === ApplicationInfo.MainStage && __sideStageEnabled) ?
652- units.gu(22) : units.gu(11)
653- height: (__sideStageEnabled) ? units.gu(22) : units.gu(19)
654- radius: "medium"
655- image: applicationImage
656- }
657-
658- ApplicationImage {
659- id: applicationImage
660- source: ApplicationManager.findApplication((application) ? application.appId : "")
661- width: shapedApplicationImage.width
662- height: shapedApplicationImage.height
663 }
664
665 UbuntuShape {
666
667=== modified file 'qml/Dash/Apps/RunningApplicationsGrid.qml'
668--- qml/Dash/Apps/RunningApplicationsGrid.qml 2014-02-26 11:04:54 +0000
669+++ qml/Dash/Apps/RunningApplicationsGrid.qml 2014-04-01 18:39:46 +0000
670@@ -18,6 +18,7 @@
671 import "../../Components"
672
673 import Ubuntu.Gestures 0.1
674+import Unity.Application 0.1
675
676 ResponsiveFlowView {
677 id: root
678@@ -25,13 +26,7 @@
679
680 signal updateScreenshots
681 property alias enableHeightBehavior: heightBehaviour.enabled
682- property bool enableHeightBehaviorOnNextCreation: firstModel.count + secondModel.count == 0
683-
684- Connections {
685- target: shell
686- onDashShownChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
687- onStageScreenshotsReadyChanged: if (shell.dashShown && shell.stageScreenshotsReady) updateScreenshots();
688- }
689+ property bool enableHeightBehaviorOnNextCreation: model.count === 0
690
691 Behavior on height {
692 id: heightBehaviour
693@@ -40,13 +35,7 @@
694 }
695
696 Connections {
697- target: root.firstModel
698- onCountChanged: {
699- heightBehaviour.enabled = true;
700- }
701- }
702- Connections {
703- target: root.secondModel
704+ target: root.model
705 onCountChanged: {
706 heightBehaviour.enabled = true;
707 }
708@@ -85,17 +74,13 @@
709 root.terminationModeEnabled = true
710 }
711 onRequestedApplicationTermination: {
712- shell.applicationManager.stopApplication(model.appId)
713+ ApplicationManager.stopApplication(model.appId)
714 }
715 onRequestedApplicationActivation: {
716- shell.activateApplication(model.appId)
717+ ApplicationManager.requestFocusApplication(model.appId)
718 }
719
720 terminationModeEnabled: root.terminationModeEnabled
721-
722- Component.onCompleted: {
723- root.updateScreenshots.connect(updateScreenshotFromCache);
724- }
725 }
726 }
727
728
729=== modified file 'qml/Dash/DashApps.qml'
730--- qml/Dash/DashApps.qml 2014-03-25 15:49:30 +0000
731+++ qml/Dash/DashApps.qml 2014-04-01 18:39:46 +0000
732@@ -17,6 +17,7 @@
733 import QtQuick 2.0
734 import Ubuntu.Components 0.1
735 import Utils 0.1
736+import Unity.Application 0.1
737 import "../Components"
738 import "../Components/ListItems"
739 import "Apps"
740@@ -25,17 +26,15 @@
741 id: scopeView
742 objectName: "DashApps"
743
744- // FIXME: a way to aggregate these models would be ideal
745- property var mainStageApplicationsModel: shell.applicationManager.mainStageApplications
746- property var sideStageApplicationModel: shell.applicationManager.sideStageApplications
747+ property var runningApps: ApplicationManager
748
749 QtObject {
750 id: countObject
751- property int count: scopeView.scope.searchQuery.length == 0 ? (mainStageApplicationsModel.count + sideStageApplicationModel.count) : 0
752+ property int count: scopeView.scope.searchQuery.length == 0 ? scopeView.runningApps.count : 0
753 }
754
755 onScopeChanged: {
756 scopeView.scope.categories.addSpecialCategory("running.apps.category", i18n.tr("Recent"), "", "{ \"template\": { \"category-layout\": \"running-apps\" } }", countObject);
757- enableHeightBehaviorOnNextCreation = (mainStageApplicationsModel.count + sideStageApplicationModel.count == 0)
758+ enableHeightBehaviorOnNextCreation = scopeView.runningApps.count === 0
759 }
760 }
761
762=== modified file 'qml/Dash/GenericScopeView.qml'
763--- qml/Dash/GenericScopeView.qml 2014-03-17 11:44:05 +0000
764+++ qml/Dash/GenericScopeView.qml 2014-04-01 18:39:46 +0000
765@@ -18,6 +18,7 @@
766 import Ubuntu.Components 0.1
767 import Utils 0.1
768 import Unity 0.2
769+import Unity.Application 0.1
770 import "../Components"
771 import "../Components/ListItems" as ListItems
772
773@@ -167,8 +168,7 @@
774 if (source.toString().indexOf("Apps/RunningApplicationsGrid.qml") != -1) {
775 // TODO: this is still a kludge :D Ideally add some kind of hook so that we
776 // can do this from DashApps.qml or think a better way that needs no special casing
777- item.firstModel = Qt.binding(function() { return mainStageApplicationsModel })
778- item.secondModel = Qt.binding(function() { return sideStageApplicationModel })
779+ item.model = Qt.binding(function() { return runningApps; })
780 item.canEnableTerminationMode = Qt.binding(function() { return scopeView.isCurrent })
781 } else {
782 item.model = Qt.binding(function() { return results })
783
784=== modified file 'qml/Launcher/Launcher.qml'
785--- qml/Launcher/Launcher.qml 2013-12-11 12:57:14 +0000
786+++ qml/Launcher/Launcher.qml 2014-04-01 18:39:46 +0000
787@@ -29,7 +29,8 @@
788 property int dragAreaWidth: units.gu(1)
789 property int minimizeDistance: units.gu(26)
790 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
791- (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
792+ (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) :
793+ (dragArea.dragging ? 0.001 : 0)
794
795 readonly property bool shown: panel.x > -panel.width
796
797
798=== modified file 'qml/Panel/Panel.qml'
799--- qml/Panel/Panel.qml 2014-01-14 15:35:57 +0000
800+++ qml/Panel/Panel.qml 2014-04-01 18:39:46 +0000
801@@ -62,7 +62,7 @@
802 height: __panelMinusSeparatorLineHeight
803 y: 0
804
805- Behavior on y { StandardAnimation {} }
806+ Behavior on y { StandardAnimation { duration: UbuntuAnimation.FastDuration } }
807 }
808
809 PanelSeparatorLine {
810
811=== modified file 'qml/Shell.qml'
812--- qml/Shell.qml 2014-03-19 22:21:57 +0000
813+++ qml/Shell.qml 2014-04-01 18:39:46 +0000
814@@ -31,7 +31,6 @@
815 import "Hud"
816 import "Components"
817 import "Bottombar"
818-import "SideStage"
819 import "Notifications"
820 import Unity.Notifications 1.0 as NotificationBackend
821
822@@ -49,20 +48,24 @@
823 readonly property real panelHeight: panel.panelHeight
824
825 property bool dashShown: dash.shown
826- property bool stageScreenshotsReady: {
827- if (sideStage.shown) {
828- if (mainStage.applications.count > 0) {
829- return mainStage.usingScreenshots && sideStage.usingScreenshots;
830- } else {
831- return sideStage.usingScreenshots;
832+
833+ property bool sideStageEnabled: shell.width >= units.gu(100)
834+ readonly property string focusedApplicationId: ApplicationManager.focusedApplicationId
835+
836+ function activateApplication(appId) {
837+ if (ApplicationManager.findApplication(appId)) {
838+ ApplicationManager.requestFocusApplication(appId);
839+ stages.show(true);
840+ if (stages.locked && ApplicationManager.focusedApplicationId == appId) {
841+ applicationsDisplayLoader.item.select(appId);
842 }
843 } else {
844- return mainStage.usingScreenshots;
845+ var execFlags = shell.sideStageEnabled ? ApplicationManager.NoFlag : ApplicationManager.ForceMainStage;
846+ ApplicationManager.startApplication(appId, execFlags);
847+ stages.show(false);
848 }
849 }
850
851- property var applicationManager: ApplicationManagerWrapper {}
852-
853 Binding {
854 target: LauncherModel
855 property: "applicationManager"
856@@ -71,49 +74,6 @@
857
858 Component.onCompleted: {
859 Theme.name = "Ubuntu.Components.Themes.SuruGradient"
860-
861- applicationManager.sideStageEnabled = Qt.binding(function() { return sideStage.enabled })
862-
863- // FIXME: if application focused before shell starts, shell draws on top of it only.
864- // We should detect already running applications on shell start and bring them to the front.
865- applicationManager.unfocusCurrentApplication();
866- }
867-
868- readonly property bool applicationFocused: !!applicationManager.mainStageFocusedApplication
869- || !!applicationManager.sideStageFocusedApplication
870- // Used for autopilot testing.
871- readonly property string currentFocusedAppId: ApplicationManager.focusedApplicationId
872-
873- readonly property bool fullscreenMode: {
874- if (greeter.shown || lockscreen.shown) {
875- return false;
876- } else if (mainStage.usingScreenshots) { // Window Manager animating so want to re-evaluate fullscreen mode
877- return mainStage.switchingFromFullscreenToFullscreen;
878- } else if (applicationManager.mainStageFocusedApplication) {
879- return applicationManager.mainStageFocusedApplication.fullscreen;
880- } else {
881- return false;
882- }
883- }
884-
885- function activateApplication(appId, argument) {
886- if (applicationManager) {
887- // For newly started applications, as it takes them time to draw their first frame
888- // we add a delay before we hide the animation screenshots to compensate.
889- var addDelay = !applicationManager.getApplicationFromDesktopFile(appId);
890-
891- var application;
892- application = applicationManager.activateApplication(appId, argument);
893- if (application == null) {
894- return;
895- }
896- if (application.stage == ApplicationInfo.MainStage || !sideStage.enabled) {
897- mainStage.activateApplication(appId, addDelay);
898- } else {
899- sideStage.activateApplication(appId, addDelay);
900- }
901- stages.show();
902- }
903 }
904
905 GSettings {
906@@ -133,244 +93,219 @@
907 Keys.onVolumeDownPressed: volumeControl.volumeDown()
908
909 Item {
910- id: underlay
911- objectName: "underlay"
912+ id: underlayClipper
913 anchors.fill: parent
914-
915- // Whether the underlay is fully covered by opaque UI elements.
916- property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
917-
918- readonly property bool applicationRunning: ((mainStage.applications && mainStage.applications.count > 0)
919- || (sideStage.applications && sideStage.applications.count > 0))
920-
921- // Whether the user should see the topmost application surface (if there's one at all).
922- readonly property bool applicationSurfaceShouldBeSeen: applicationRunning && !stages.fullyHidden
923- && !mainStage.usingScreenshots // but want sideStage animating over app surface
924-
925-
926-
927- // NB! Application surfaces are stacked behing the shell one. So they can only be seen by the user
928- // through the translucent parts of the shell surface.
929- visible: !fullyCovered && !applicationSurfaceShouldBeSeen
930-
931- Rectangle {
932- anchors.fill: parent
933- color: "black"
934- opacity: dash.disappearingAnimationProgress
935- }
936-
937- Image {
938- anchors.fill: dash
939- source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
940- fillMode: Image.PreserveAspectCrop
941- horizontalAlignment: Image.AlignRight
942- verticalAlignment: Image.AlignTop
943- }
944-
945- Dash {
946- id: dash
947- objectName: "dash"
948-
949- available: !greeter.shown && !lockscreen.shown
950- hides: [stages, launcher, panel.indicators]
951- shown: disappearingAnimationProgress !== 1.0
952- enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
953- // FIXME: unfocus all applications when going back to the dash
954- onEnabledChanged: {
955- if (enabled) {
956- shell.applicationManager.unfocusCurrentApplication()
957- }
958- }
959-
960- anchors {
961- fill: parent
962- topMargin: panel.panelHeight
963- }
964-
965- contentScale: 1.0 - 0.2 * disappearingAnimationProgress
966- opacity: 1.0 - disappearingAnimationProgress
967- property real disappearingAnimationProgress: {
968- if (greeter.shown) {
969- return greeter.showProgress;
970+ anchors.rightMargin: stages.overlayWidth
971+ clip: stages.overlayMode && !stages.painting
972+
973+ InputFilterArea {
974+ anchors.fill: parent
975+ blockInput: parent.clip
976+ }
977+
978+ Item {
979+ id: underlay
980+ objectName: "underlay"
981+ anchors.fill: parent
982+ anchors.rightMargin: -parent.anchors.rightMargin
983+
984+ // Whether the underlay is fully covered by opaque UI elements.
985+ property bool fullyCovered: panel.indicators.fullyOpened && shell.width <= panel.indicatorsMenuWidth
986+
987+ // Whether the user should see the topmost application surface (if there's one at all).
988+ readonly property bool applicationSurfaceShouldBeSeen: stages.shown && !stages.painting && !stages.overlayMode
989+
990+ // NB! Application surfaces are stacked behind the shell one. So they can only be seen by the user
991+ // through the translucent parts of the shell surface.
992+ visible: !fullyCovered && !applicationSurfaceShouldBeSeen
993+
994+ CrossFadeImage {
995+ id: backgroundImage
996+ objectName: "backgroundImage"
997+
998+ anchors.fill: parent
999+ source: shell.background
1000+ fillMode: Image.PreserveAspectCrop
1001+ }
1002+
1003+ Rectangle {
1004+ anchors.fill: parent
1005+ color: "black"
1006+ opacity: dash.disappearingAnimationProgress
1007+ }
1008+
1009+ Image {
1010+ anchors.fill: dash
1011+ source: shell.width > shell.height ? "Dash/graphics/paper_landscape.png" : "Dash/graphics/paper_portrait.png"
1012+ fillMode: Image.PreserveAspectCrop
1013+ horizontalAlignment: Image.AlignRight
1014+ verticalAlignment: Image.AlignTop
1015+ }
1016+
1017+ Dash {
1018+ id: dash
1019+ objectName: "dash"
1020+
1021+ available: !greeter.shown && !lockscreen.shown
1022+ hides: [stages, launcher, panel.indicators]
1023+ shown: disappearingAnimationProgress !== 1.0
1024+ enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
1025+
1026+ anchors {
1027+ fill: parent
1028+ topMargin: panel.panelHeight
1029+ }
1030+
1031+ contentScale: 1.0 - 0.2 * disappearingAnimationProgress
1032+ opacity: 1.0 - disappearingAnimationProgress
1033+ property real disappearingAnimationProgress: {
1034+ if (greeter.shown) {
1035+ return greeter.showProgress;
1036+ } else {
1037+ if (stages.overlayMode) {
1038+ return 0;
1039+ }
1040+ return stages.showProgress
1041+ }
1042+ }
1043+
1044+ // FIXME: only necessary because stages.showProgress and
1045+ // greeterRevealer.animatedProgress are not animated
1046+ Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
1047+ }
1048+ }
1049+ }
1050+
1051+ EdgeDragArea {
1052+ id: stagesDragHandle
1053+ direction: Direction.Leftwards
1054+
1055+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1056+ width: shell.edgeSize
1057+
1058+ property real progress: stages.width
1059+
1060+ onTouchXChanged: {
1061+ if (status == DirectionalDragArea.Recognized) {
1062+ if (ApplicationManager.count == 0) {
1063+ progress = Math.max(stages.width - stagesDragHandle.width + touchX, stages.width * .3)
1064 } else {
1065- return stagesOuterContainer.showProgress;
1066+ progress = stages.width - stagesDragHandle.width + touchX
1067 }
1068 }
1069+ }
1070
1071- // FIXME: only necessary because stagesOuterContainer.showProgress and
1072- // greeterRevealer.animatedProgress are not animated
1073- Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
1074+ onDraggingChanged: {
1075+ if (!dragging) {
1076+ if (ApplicationManager.count > 0 && progress < stages.width - units.gu(10)) {
1077+ stages.show(true)
1078+ }
1079+ stagesDragHandle.progress = stages.width;
1080+ }
1081 }
1082 }
1083
1084 Item {
1085- id: stagesOuterContainer
1086-
1087+ id: stages
1088+ objectName: "stages"
1089 width: parent.width
1090 height: parent.height
1091- x: launcher.progress
1092- Behavior on x {SmoothedAnimation{velocity: 600}}
1093-
1094- property real showProgress:
1095- MathUtils.clamp(1 - (x + stages.x) / shell.width, 0, 1)
1096-
1097- Showable {
1098- id: stages
1099- objectName: "stages"
1100-
1101- x: width
1102-
1103- property bool fullyShown: shown && x == 0 && parent.x == 0
1104- property bool fullyHidden: !shown && x == width
1105- available: !greeter.shown
1106- hides: [panel.indicators]
1107- shown: false
1108- opacity: 1.0
1109- showAnimation: StandardAnimation { property: "x"; duration: 350; to: 0; easing.type: Easing.OutCubic }
1110- hideAnimation: StandardAnimation { property: "x"; duration: 350; to: width; easing.type: Easing.OutCubic }
1111-
1112- width: parent.width
1113- height: parent.height
1114-
1115- // close the stages when no focused application remains
1116- Connections {
1117- target: shell.applicationManager
1118- onMainStageFocusedApplicationChanged: stages.closeIfNoApplications()
1119- onSideStageFocusedApplicationChanged: stages.closeIfNoApplications()
1120- ignoreUnknownSignals: true
1121- }
1122-
1123- function closeIfNoApplications() {
1124- if (!shell.applicationManager.mainStageFocusedApplication
1125- && !shell.applicationManager.sideStageFocusedApplication
1126- && shell.applicationManager.mainStageApplications.count == 0
1127- && shell.applicationManager.sideStageApplications.count == 0) {
1128+
1129+ x: {
1130+ if (shown) {
1131+ if (overlayMode || locked) {
1132+ return 0;
1133+ }
1134+ return launcher.progress
1135+ } else {
1136+ return stagesDragHandle.progress
1137+ }
1138+ }
1139+
1140+ Behavior on x { SmoothedAnimation { velocity: 600; duration: UbuntuAnimation.FastDuration } }
1141+
1142+ property bool shown: false
1143+
1144+ property real showProgress: overlayMode ? 0 : MathUtils.clamp(1 - x / shell.width, 0, 1)
1145+
1146+ property bool fullyShown: x == 0
1147+ property bool fullyHidden: x == width
1148+
1149+ property bool painting: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.painting : false
1150+ property bool fullscreen: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.fullscreen : false
1151+ property bool overlayMode: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayMode : false
1152+ property int overlayWidth: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.overlayWidth : false
1153+ property bool locked: applicationsDisplayLoader.item ? applicationsDisplayLoader.item.locked : false
1154+
1155+ function show(focusApp) {
1156+ shown = true;
1157+ panel.indicators.hide();
1158+ if (!ApplicationManager.focusedApplicationId && ApplicationManager.count > 0 && focusApp) {
1159+ ApplicationManager.focusApplication(ApplicationManager.get(0).appId);
1160+ }
1161+ }
1162+
1163+ function hide() {
1164+ shown = false;
1165+ if (ApplicationManager.focusedApplicationId) {
1166+ ApplicationManager.unfocusCurrentApplication();
1167+ }
1168+ }
1169+
1170+ Connections {
1171+ target: ApplicationManager
1172+
1173+ onFocusRequested: {
1174+ stages.show(true);
1175+ }
1176+
1177+ onFocusedApplicationIdChanged: {
1178+ if (ApplicationManager.focusedApplicationId.length > 0) {
1179+ stages.show(false);
1180+ } else {
1181+ if (!stages.overlayMode) {
1182+ stages.hide();
1183+ }
1184+ }
1185+
1186+ // If any app is focused when greeter is open, it's due to a user action
1187+ // like a snap decision (say, an incoming call).
1188+ // TODO: these should be protected to only unlock for certain applications / certain usecases
1189+ // potentially only in connection with a notification.
1190+ greeter.hide()
1191+ }
1192+
1193+ onApplicationAdded: {
1194+ stages.show(false);
1195+ }
1196+
1197+ onApplicationRemoved: {
1198+ if (ApplicationManager.focusedApplicationId.length == 0) {
1199 stages.hide();
1200 }
1201 }
1202-
1203- // show the stages when an application gets the focus
1204- Connections {
1205- target: shell.applicationManager
1206- onMainStageFocusedApplicationChanged: {
1207- if (shell.applicationManager.mainStageFocusedApplication) {
1208- mainStage.show();
1209- stages.show();
1210- }
1211- }
1212- onSideStageFocusedApplicationChanged: {
1213- if (shell.applicationManager.sideStageFocusedApplication) {
1214- sideStage.show();
1215- stages.show();
1216- }
1217- }
1218- ignoreUnknownSignals: true
1219- }
1220-
1221- Stage {
1222- id: mainStage
1223-
1224- anchors.fill: parent
1225- fullyShown: stages.fullyShown
1226- fullyHidden: stages.fullyHidden
1227- shouldUseScreenshots: !fullyShown
1228- rightEdgeEnabled: !sideStage.enabled
1229-
1230- applicationManager: shell.applicationManager
1231- rightEdgeDraggingAreaWidth: shell.edgeSize
1232- normalApplicationY: shell.panelHeight
1233-
1234- shown: true
1235- function show() {
1236- stages.show();
1237- }
1238- function hide() {
1239- }
1240-
1241- // FIXME: workaround the fact that focusing a main stage application
1242- // raises its surface on top of all other surfaces including the ones
1243- // that belong to side stage applications.
1244- onFocusedApplicationChanged: {
1245- if (focusedApplication && sideStage.focusedApplication && sideStage.fullyShown) {
1246- shell.applicationManager.focusApplication(sideStage.focusedApplication);
1247- }
1248- }
1249- }
1250-
1251- SideStage {
1252- id: sideStage
1253-
1254- applicationManager: shell.applicationManager
1255- rightEdgeDraggingAreaWidth: shell.edgeSize
1256- normalApplicationY: shell.panelHeight
1257-
1258- onShownChanged: {
1259- if (!shown && mainStage.applications.count == 0) {
1260- stages.hide();
1261- }
1262- }
1263- // FIXME: when hiding the side stage, refocus the main stage
1264- // application so that it goes in front of the side stage
1265- // application and hides it
1266- onFullyShownChanged: {
1267- if (!fullyShown && stages.fullyShown && sideStage.focusedApplication != null) {
1268- shell.applicationManager.focusApplication(mainStage.focusedApplication);
1269- }
1270- }
1271-
1272- enabled: shell.width >= units.gu(100)
1273- visible: enabled
1274- fullyShown: stages.fullyShown && shown
1275- && sideStage[sideStageRevealer.boundProperty] == sideStageRevealer.openedValue
1276- shouldUseScreenshots: !fullyShown || mainStage.usingScreenshots || sideStageRevealer.pressed
1277-
1278- available: !greeter.shown && !lockscreen.shown && enabled
1279- hides: [launcher, panel.indicators]
1280- shown: false
1281- showAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.openedValue; easing.type: Easing.OutQuint }
1282- hideAnimation: StandardAnimation { property: "x"; duration: 350; to: sideStageRevealer.closedValue; easing.type: Easing.OutQuint }
1283-
1284- width: units.gu(40)
1285- height: stages.height
1286- handleExpanded: sideStageRevealer.pressed
1287- }
1288-
1289- Revealer {
1290- id: sideStageRevealer
1291-
1292- enabled: mainStage.applications.count > 0 && sideStage.applications.count > 0
1293- && sideStage.available
1294- direction: Qt.RightToLeft
1295- openedValue: parent.width - sideStage.width
1296- hintDisplacement: units.gu(3)
1297- /* The size of the sidestage handle needs to be bigger than the
1298- typical size used for edge detection otherwise it is really
1299- hard to grab.
1300- */
1301- handleSize: sideStage.shown ? units.gu(4) : shell.edgeSize
1302- closedValue: parent.width + sideStage.handleSizeCollapsed
1303- target: sideStage
1304- x: parent.width - width
1305- width: sideStage.width + handleSize * 0.7
1306- height: sideStage.height
1307- orientation: Qt.Horizontal
1308- }
1309-
1310- DragHandle {
1311- id: stagesDragHandle
1312-
1313- anchors.top: parent.top
1314- anchors.bottom: parent.bottom
1315- anchors.right: parent.left
1316-
1317- width: shell.edgeSize
1318- direction: Direction.Leftwards
1319- enabled: greeter.showProgress == 0 && edgeDemo.dashEnabled
1320- property bool haveApps: mainStage.applications.count > 0 || sideStage.applications.count > 0
1321-
1322- maxTotalDragDistance: haveApps ? parent.width : parent.width * 0.7
1323- // Make autocompletion impossible when !haveApps
1324- edgeDragEvaluator.minDragDistance: haveApps ? maxTotalDragDistance * 0.1 : Number.MAX_VALUE
1325+ }
1326+
1327+ Loader {
1328+ id: applicationsDisplayLoader
1329+ anchors.fill: parent
1330+
1331+ source: shell.sideStageEnabled ? "Stages/StageWithSideStage.qml" : "Stages/PhoneStage.qml"
1332+
1333+ Binding {
1334+ target: applicationsDisplayLoader.item
1335+ property: "moving"
1336+ value: !stages.fullyShown
1337+ }
1338+ Binding {
1339+ target: applicationsDisplayLoader.item
1340+ property: "shown"
1341+ value: stages.shown
1342+ }
1343+ Binding {
1344+ target: applicationsDisplayLoader.item
1345+ property: "dragAreaWidth"
1346+ value: shell.edgeSize
1347 }
1348 }
1349 }
1350@@ -447,26 +382,6 @@
1351
1352 dragHandleWidth: shell.edgeSize
1353
1354- property var previousMainApp: null
1355- property var previousSideApp: null
1356-
1357- function removeApplicationFocus() {
1358- greeter.previousMainApp = applicationManager.mainStageFocusedApplication;
1359- greeter.previousSideApp = applicationManager.sideStageFocusedApplication;
1360- applicationManager.unfocusCurrentApplication();
1361- }
1362-
1363- function restoreApplicationFocus() {
1364- if (greeter.previousMainApp) {
1365- applicationManager.focusApplication(greeter.previousMainApp);
1366- greeter.previousMainApp = null;
1367- }
1368- if (greeter.previousSideApp) {
1369- applicationManager.focusApplication(greeter.previousSideApp);
1370- greeter.previousSideApp = null;
1371- }
1372- }
1373-
1374 onShownChanged: {
1375 if (shown) {
1376 lockscreen.reset();
1377@@ -476,9 +391,6 @@
1378 LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
1379 }
1380 greeter.forceActiveFocus();
1381- removeApplicationFocus();
1382- } else {
1383- restoreApplicationFocus();
1384 }
1385 }
1386
1387@@ -496,33 +408,16 @@
1388 }
1389 }
1390
1391- Connections {
1392- target: applicationManager
1393- ignoreUnknownSignals: true
1394- // If any app is focused when greeter is open, it's due to a user action
1395- // like a snap decision (say, an incoming call).
1396- // TODO: these should be protected to only unlock for certain applications / certain usecases
1397- // potentially only in connection with a notification.
1398- onMainStageFocusedApplicationChanged: {
1399- if (greeter.shown && applicationManager.mainStageFocusedApplication) {
1400- greeter.previousMainApp = null // make way for new focused app
1401- greeter.previousSideApp = null
1402- greeter.hide()
1403- }
1404- }
1405- onSideStageFocusedApplicationChanged: {
1406- if (greeter.shown && applicationManager.sideStageFocusedApplication) {
1407- greeter.previousMainApp = null // make way for new focused app
1408- greeter.previousSideApp = null
1409- greeter.hide()
1410- }
1411- }
1412+ Binding {
1413+ target: ApplicationManager
1414+ property: "suspended"
1415+ value: greeter.shown && greeter.showProgress == 1
1416 }
1417 }
1418
1419 InputFilterArea {
1420 anchors.fill: parent
1421- blockInput: !applicationFocused || greeter.shown || lockscreen.shown || launcher.shown
1422+ blockInput: ApplicationManager.focusedApplicationId.length === 0 || greeter.shown || lockscreen.shown || launcher.shown
1423 || panel.indicators.shown || hud.shown
1424 }
1425
1426@@ -571,7 +466,9 @@
1427 available: edgeDemo.panelEnabled
1428 contentEnabled: edgeDemo.panelContentEnabled
1429 }
1430- fullscreenMode: shell.fullscreenMode
1431+ property string focusedAppId: ApplicationManager.focusedApplicationId
1432+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
1433+ fullscreenMode: focusedApplication && stages.fullscreen && !greeter.shown && !lockscreen.shown
1434 searchVisible: !greeter.shown && !lockscreen.shown && dash.shown && dash.searchable
1435
1436 InputFilterArea {
1437@@ -597,9 +494,8 @@
1438 hideAnimation: StandardAnimation { property: "y"; duration: hud.showableAnimationDuration; to: hudRevealer.closedValue; easing.type: Easing.Linear }
1439
1440 Connections {
1441- target: shell.applicationManager
1442- onMainStageFocusedApplicationChanged: hud.hide()
1443- onSideStageFocusedApplicationChanged: hud.hide()
1444+ target: ApplicationManager
1445+ onFocusedApplicationIdChanged: hud.hide()
1446 }
1447 }
1448
1449@@ -624,7 +520,7 @@
1450 theHud: hud
1451 anchors.fill: parent
1452 enabled: hud.available
1453- applicationIsOnForeground: applicationFocused
1454+ applicationIsOnForeground: ApplicationManager.focusedApplicationId
1455 }
1456
1457 InputFilterArea {
1458@@ -655,9 +551,11 @@
1459 showHome()
1460 }
1461 onDash: {
1462- if (stages.shown) {
1463- stages.hide();
1464- launcher.hide();
1465+ if (stages.shown && !stages.overlayMode) {
1466+ if (!stages.locked) {
1467+ stages.hide();
1468+ launcher.hide();
1469+ }
1470 }
1471 }
1472 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
1473@@ -750,14 +648,14 @@
1474 anchors.bottom: parent.bottom
1475 anchors.left: parent.left
1476 anchors.right: parent.right
1477- height: shell.applicationManager ? shell.applicationManager.keyboardHeight : 0
1478+ height: ApplicationManager.keyboardVisible ? ApplicationManager.keyboardHeight : 0
1479
1480- enabled: shell.applicationManager && shell.applicationManager.keyboardVisible
1481+ enabled: ApplicationManager.keyboardVisible
1482 }
1483
1484 Label {
1485 anchors.centerIn: parent
1486- visible: applicationManager.fake
1487+ visible: ApplicationManager.fake ? ApplicationManager.fake : false
1488 text: "EARLY ALPHA\nNOT READY FOR USE"
1489 color: "lightgrey"
1490 opacity: 0.2
1491
1492=== removed directory 'qml/SideStage'
1493=== removed file 'qml/SideStage/SideStage.qml'
1494--- qml/SideStage/SideStage.qml 2014-02-03 13:31:28 +0000
1495+++ qml/SideStage/SideStage.qml 1970-01-01 00:00:00 +0000
1496@@ -1,73 +0,0 @@
1497-/*
1498- * Copyright (C) 2013 Canonical, Ltd.
1499- *
1500- * This program is free software; you can redistribute it and/or modify
1501- * it under the terms of the GNU General Public License as published by
1502- * the Free Software Foundation; version 3.
1503- *
1504- * This program is distributed in the hope that it will be useful,
1505- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1506- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1507- * GNU General Public License for more details.
1508- *
1509- * You should have received a copy of the GNU General Public License
1510- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1511- */
1512-
1513-import QtQuick 2.0
1514-import Unity.Application 0.1
1515-import Ubuntu.Components 0.1
1516-import "../Components"
1517-
1518-Stage {
1519- id: stage
1520-
1521- type: ApplicationInfo.SideStage
1522- property real handleSizeCollapsed: units.gu(2.5)
1523- property real handleSizeExpanded: units.gu(4)
1524- property bool handleExpanded
1525-
1526- /* FIXME: workaround so that when a main stage app goes fullscreen
1527- the sidestage's top is not transparent. Proper fix would be to
1528- resize the side stage app.
1529- */
1530- Rectangle {
1531- id: backgroundBeneathPanel
1532- anchors {
1533- top: parent.top
1534- left: parent.left
1535- right: parent.right
1536- }
1537- height: shell.panelHeight
1538- color: background.color
1539- z: -1
1540- }
1541-
1542- Rectangle {
1543- id: background
1544- anchors.fill: parent
1545- color: "#2c2924"
1546- z: -1
1547- visible: stage.usingScreenshots
1548- }
1549-
1550- SidestageHandle {
1551- id: handle
1552- objectName: "sideStageHandle"
1553-
1554- anchors {
1555- top: parent.top
1556- bottom: parent.bottom
1557- left: parent.left
1558- leftMargin: -width
1559- }
1560- width: handleExpanded ? handleSizeExpanded : handleSizeCollapsed
1561- Behavior on width { NumberAnimation { easing.type: Easing.OutQuart} }
1562- z: -1
1563- }
1564-
1565- InputFilterArea {
1566- anchors.fill: handle
1567- blockInput: visible
1568- }
1569-}
1570
1571=== removed directory 'qml/SideStage/graphics'
1572=== added directory 'qml/Stages'
1573=== added file 'qml/Stages/PhoneStage.qml'
1574--- qml/Stages/PhoneStage.qml 1970-01-01 00:00:00 +0000
1575+++ qml/Stages/PhoneStage.qml 2014-04-01 18:39:46 +0000
1576@@ -0,0 +1,499 @@
1577+/*
1578+ * Copyright (C) 2014 Canonical, Ltd.
1579+ *
1580+ * This program is free software; you can redistribute it and/or modify
1581+ * it under the terms of the GNU General Public License as published by
1582+ * the Free Software Foundation; version 3.
1583+ *
1584+ * This program is distributed in the hope that it will be useful,
1585+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1586+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1587+ * GNU General Public License for more details.
1588+ *
1589+ * You should have received a copy of the GNU General Public License
1590+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1591+ */
1592+
1593+import QtQuick 2.0
1594+import Ubuntu.Components 0.1
1595+import Ubuntu.Gestures 0.1
1596+import Unity.Application 0.1
1597+import Utils 0.1
1598+import "../Components"
1599+
1600+Item {
1601+ id: root
1602+
1603+ // Controls to be set from outside
1604+ property bool shown: false
1605+ property bool moving: false
1606+ property int dragAreaWidth
1607+
1608+ // State information propagated to the outside
1609+ readonly property bool painting: mainScreenshotImage.visible || fadeInScreenshotImage.visible || appSplash.visible || spreadView.visible
1610+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
1611+ property bool locked: spreadView.visible
1612+
1613+ // Not used for PhoneStage, only useful for SideStage and similar
1614+ property bool overlayMode: false
1615+ property int overlayWidth: 0
1616+
1617+ function select(appId) {
1618+ spreadView.snapTo(priv.indexOf(appId))
1619+ }
1620+
1621+ onMovingChanged: {
1622+ if (moving) {
1623+ if (ApplicationManager.focusedApplicationId) {
1624+ priv.requestNewScreenshot();
1625+ } else {
1626+ mainScreenshotImage.anchors.leftMargin = 0;
1627+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
1628+ mainScreenshotImage.visible = true;
1629+ }
1630+ } else {
1631+ mainScreenshotImage.visible = false;
1632+ }
1633+ }
1634+
1635+ Connections {
1636+ target: ApplicationManager
1637+
1638+ onFocusRequested: {
1639+ if (spreadView.visible) {
1640+ spreadView.snapTo(priv.indexOf(appId));
1641+ } else {
1642+ priv.switchToApp(appId);
1643+ }
1644+ }
1645+
1646+ onFocusedApplicationIdChanged: {
1647+ if (ApplicationManager.focusedApplicationId.length > 0) {
1648+ if (priv.secondApplicationStarting || priv.applicationStarting) {
1649+ appSplashTimer.restart();
1650+ } else {
1651+ var application = priv.focusedApplication;
1652+ root.fullscreen = application.fullscreen;
1653+ mainScreenshotImage.source = application.screenshot;
1654+ }
1655+ } else {
1656+ spreadView.selectedIndex = -1;
1657+ spreadView.phase = 0;
1658+ spreadView.contentX = -spreadView.shift;
1659+ }
1660+ }
1661+
1662+ onApplicationAdded: {
1663+ if (!priv.focusedApplication) {
1664+ mainScreenshotImage.source = "";
1665+ mainScreenshotImage.visible = false;
1666+ priv.applicationStarting = true;
1667+ } else {
1668+ mainScreenshotImage.source = "";
1669+ priv.newFocusedAppId = appId;
1670+ priv.secondApplicationStarting = true;
1671+ priv.requestNewScreenshot();
1672+ }
1673+
1674+ if (spreadView.visible) {
1675+ spreadView.snapTo(0);
1676+ }
1677+ }
1678+
1679+ onApplicationRemoved: {
1680+ if (ApplicationManager.count == 0) {
1681+ mainScreenshotImage.source = ""
1682+ mainScreenshotImage.visible = false;
1683+ } else {
1684+ mainScreenshotImage.source = ApplicationManager.get(0).screenshot;
1685+ }
1686+ }
1687+ }
1688+
1689+ QtObject {
1690+ id: priv
1691+
1692+ property string focusedAppId: ApplicationManager.focusedApplicationId
1693+ property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
1694+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
1695+
1696+ property bool waitingForScreenshot: false
1697+
1698+ property bool applicationStarting: false
1699+ property bool secondApplicationStarting: false
1700+
1701+ property string newFocusedAppId
1702+
1703+ onFocusedScreenshotChanged: {
1704+ if (root.moving && priv.waitingForScreenshot) {
1705+ mainScreenshotImage.anchors.leftMargin = 0;
1706+ mainScreenshotImage.source = priv.focusedScreenshot
1707+ mainScreenshotImage.visible = true;
1708+ } else if (priv.secondApplicationStarting && priv.waitingForScreenshot) {
1709+ applicationSwitchingAnimation.start();
1710+ }
1711+ waitingForScreenshot = false;
1712+ }
1713+
1714+ function requestNewScreenshot() {
1715+ waitingForScreenshot = true;
1716+ ApplicationManager.updateScreenshot(focusedAppId);
1717+ }
1718+
1719+ function switchToApp(appId) {
1720+ if (priv.focusedAppId) {
1721+ priv.newFocusedAppId = appId;
1722+ root.fullscreen = ApplicationManager.findApplication(appId).fullscreen;
1723+ applicationSwitchingAnimation.start();
1724+ } else {
1725+ ApplicationManager.focusApplication(appId);
1726+ }
1727+ }
1728+
1729+ function indexOf(appId) {
1730+ for (var i = 0; i < ApplicationManager.count; i++) {
1731+ if (ApplicationManager.get(i).appId == appId) {
1732+ return i;
1733+ }
1734+ }
1735+ return -1;
1736+ }
1737+
1738+ }
1739+
1740+ // FIXME: the signal connections seems to get lost.
1741+ Connections {
1742+ target: priv.focusedApplication
1743+ onScreenshotChanged: priv.focusedScreenshot = priv.focusedApplication.screenshot
1744+ }
1745+ Binding {
1746+ target: root
1747+ property: "fullscreen"
1748+ value: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
1749+ }
1750+
1751+ Timer {
1752+ id: appSplashTimer
1753+ // FIXME: We really need to show something meaningful in the app surface instead of guessing
1754+ // when it might be ready
1755+ interval: 500
1756+ repeat: false
1757+ onTriggered: {
1758+ priv.applicationStarting = false;
1759+ priv.secondApplicationStarting = false;
1760+ }
1761+ }
1762+
1763+ SequentialAnimation {
1764+ id: applicationSwitchingAnimation
1765+ // setup
1766+ PropertyAction { target: mainScreenshotImage; property: "anchors.leftMargin"; value: 0 }
1767+ PropertyAction { target: mainScreenshotImage; property: "source"; value: priv.focusedScreenshot }
1768+ PropertyAction { targets: [mainScreenshotImage, fadeInScreenshotImage]; property: "visible"; value: true }
1769+ PropertyAction { target: fadeInScreenshotImage; property: "source"; value: {
1770+ var newFocusedApp = ApplicationManager.findApplication(priv.newFocusedAppId);
1771+ return newFocusedApp ? newFocusedApp.screenshot : "" }
1772+ }
1773+ PropertyAction { target: fadeInScreenshotImage; property: "opacity"; value: 0 }
1774+ PropertyAction { target: fadeInScreenshotImage; property: "scale"; value: .8 }
1775+
1776+
1777+ // The actual animation
1778+ ParallelAnimation {
1779+ UbuntuNumberAnimation { target: mainScreenshotImage; property: "anchors.leftMargin"; to: root.width; duration: UbuntuAnimation.SlowDuration }
1780+ UbuntuNumberAnimation { target: fadeInScreenshotImage; properties: "opacity,scale"; to: 1; duration: UbuntuAnimation.SlowDuration }
1781+ }
1782+
1783+ // restore stuff
1784+ ScriptAction { script: ApplicationManager.focusApplication(priv.newFocusedAppId); }
1785+ PropertyAction { target: fadeInScreenshotImage; property: "visible"; value: false }
1786+ PropertyAction { target: mainScreenshotImage; property: "visible"; value: false }
1787+ }
1788+
1789+ // FIXME: Drop this and make the imageprovider show a splashscreen instead
1790+ Rectangle {
1791+ id: appSplash2
1792+ anchors.fill: parent
1793+ color: "white"
1794+ visible: priv.secondApplicationStarting
1795+ }
1796+ Image {
1797+ id: fadeInScreenshotImage
1798+ anchors { left: parent.left; bottom: parent.bottom }
1799+ width: parent.width
1800+ scale: .7
1801+ visible: false
1802+ }
1803+
1804+ Rectangle {
1805+ id: appSplash
1806+ anchors.fill: parent
1807+ color: "white"
1808+ visible: priv.applicationStarting
1809+ }
1810+ Image {
1811+ id: mainScreenshotImage
1812+ anchors { left: parent.left; bottom: parent.bottom }
1813+ width: parent.width
1814+ visible: false
1815+ }
1816+
1817+ EdgeDragArea {
1818+ id: spreadDragArea
1819+ direction: Direction.Leftwards
1820+ enabled: ApplicationManager.count > 1 && spreadView.phase != 2
1821+
1822+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
1823+ width: root.dragAreaWidth
1824+
1825+ // Sitting at the right edge of the screen, this EdgeDragArea directly controls the spreadView when
1826+ // attachedToView is true. When the finger movement passes positionMarker3 we detach it from the
1827+ // spreadView and make the spreadView snap to positionMarker4.
1828+ property bool attachedToView: true
1829+
1830+ property var gesturePoints: new Array()
1831+
1832+ onTouchXChanged: {
1833+ if (!dragging && !priv.waitingForScreenshot) {
1834+ // Initial touch. Let's update the screenshot and reset the spreadView to the starting position.
1835+ priv.requestNewScreenshot();
1836+ spreadView.phase = 0;
1837+ spreadView.contentX = -spreadView.shift;
1838+ }
1839+ if (dragging && attachedToView) {
1840+ // Gesture recognized. Let's move the spreadView with the finger
1841+ spreadView.contentX = -touchX - spreadView.shift;
1842+ }
1843+ if (attachedToView && spreadView.shiftedContentX >= spreadView.width * spreadView.positionMarker3) {
1844+ // We passed positionMarker3. Detach from spreadView and snap it.
1845+ attachedToView = false;
1846+ spreadView.snap();
1847+ }
1848+ gesturePoints.push(touchX);
1849+ }
1850+
1851+ onStatusChanged: {
1852+ if (status == DirectionalDragArea.Recognized) {
1853+ attachedToView = true;
1854+ }
1855+ }
1856+
1857+ onDraggingChanged: {
1858+ if (dragging) {
1859+ // Gesture recognized. Start recording this gesture
1860+ gesturePoints = [];
1861+ return;
1862+ }
1863+
1864+ // Ok. The user released. Find out if it was a one-way movement.
1865+ var oneWayFlick = true;
1866+ var smallestX = spreadDragArea.width;
1867+ for (var i = 0; i < gesturePoints.length; i++) {
1868+ if (gesturePoints[i] >= smallestX) {
1869+ oneWayFlick = false;
1870+ break;
1871+ }
1872+ smallestX = gesturePoints[i];
1873+ }
1874+ gesturePoints = [];
1875+
1876+ if (oneWayFlick && spreadView.shiftedContentX > units.gu(2) &&
1877+ spreadView.shiftedContentX < spreadView.positionMarker1 * spreadView.width) {
1878+ // If it was a short one-way movement, do the Alt+Tab switch
1879+ // no matter if we didn't cross positionMarker1 yet.
1880+ spreadView.snapTo(1);
1881+ } else if (!dragging && attachedToView) {
1882+ // otherwise snap to the closest snap position we can find
1883+ // (might be back to start, to app 1 or to spread)
1884+ spreadView.snap();
1885+ }
1886+ }
1887+ }
1888+
1889+ Rectangle {
1890+ id: coverFlipBackground
1891+ anchors.fill: parent
1892+ color: "black"
1893+ visible: spreadView.visible
1894+ }
1895+
1896+ InputFilterArea {
1897+ anchors.fill: root
1898+ blockInput: spreadView.visible
1899+ }
1900+
1901+ Flickable {
1902+ id: spreadView
1903+ objectName: "spreadView"
1904+ anchors.fill: parent
1905+ visible: spreadDragArea.status == DirectionalDragArea.Recognized || phase > 1 || snapAnimation.running
1906+ contentWidth: spreadRow.width - shift
1907+ contentX: -shift
1908+
1909+ // The flickable needs to fill the screen in order to get touch events all over.
1910+ // However, we don't want to the user to be able to scroll back all the way. For
1911+ // that, the beginning of the gesture starts with a negative value for contentX
1912+ // so the flickable wants to pull it into the view already. "shift" tunes the
1913+ // distance where to "lock" the content.
1914+ property real shift: width / 2
1915+ property real shiftedContentX: contentX + shift
1916+
1917+ property int tileDistance: width / 4
1918+
1919+ // Those markers mark the various positions in the spread (ratio to screen width from right to left):
1920+ // 0 - 1: following finger, snap back to the beginning on release
1921+ property real positionMarker1: 0.3
1922+ // 1 - 2: curved snapping movement, snap to app 1 on release
1923+ property real positionMarker2: 0.45
1924+ // 2 - 3: movement follows finger, snaps back to app 1 on release
1925+ property real positionMarker3: 0.6
1926+ // passing 3, we detach movement from the finger and snap to 4
1927+ property real positionMarker4: 0.9
1928+
1929+ // This is where the first app snaps to when bringing it in from the right edge.
1930+ property real snapPosition: 0.75
1931+
1932+ // Phase of the animation:
1933+ // 0: Starting from right edge, a new app (index 1) comes in from the right
1934+ // 1: The app has reached the first snap position.
1935+ // 2: The list is dragged further and snaps into the spread view when entering phase 2
1936+ property int phase: 0
1937+
1938+ property int selectedIndex: -1
1939+
1940+ onShiftedContentXChanged: {
1941+ switch (phase) {
1942+ case 0:
1943+ if (shiftedContentX > width * positionMarker2) {
1944+ phase = 1;
1945+ }
1946+ break;
1947+ case 1:
1948+ if (shiftedContentX < width * positionMarker2) {
1949+ phase = 0;
1950+ } else if (shiftedContentX >= width * positionMarker4) {
1951+ phase = 2;
1952+ }
1953+ break;
1954+ }
1955+ }
1956+
1957+ function snap() {
1958+ if (shiftedContentX < positionMarker1 * width) {
1959+ snapAnimation.targetContentX = -shift;
1960+ snapAnimation.start();
1961+ } else if (shiftedContentX < positionMarker2 * width) {
1962+ snapTo(1)
1963+ } else if (shiftedContentX < positionMarker3 * width) {
1964+ snapTo(1)
1965+ } else if (phase < 2){
1966+ // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
1967+ snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
1968+ snapAnimation.start();
1969+ }
1970+ }
1971+ function snapTo(index) {
1972+ spreadView.selectedIndex = index;
1973+ root.fullscreen = ApplicationManager.get(index).fullscreen;
1974+ snapAnimation.targetContentX = -shift;
1975+ snapAnimation.start();
1976+ }
1977+
1978+ SequentialAnimation {
1979+ id: snapAnimation
1980+ property int targetContentX: -spreadView.shift
1981+
1982+ UbuntuNumberAnimation {
1983+ target: spreadView
1984+ property: "contentX"
1985+ to: snapAnimation.targetContentX
1986+ duration: UbuntuAnimation.FastDuration
1987+ }
1988+
1989+ ScriptAction {
1990+ script: {
1991+ if (spreadView.selectedIndex >= 0) {
1992+ ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
1993+ spreadView.selectedIndex = -1
1994+ spreadView.phase = 0;
1995+ spreadView.contentX = -spreadView.shift;
1996+ }
1997+ }
1998+ }
1999+ }
2000+
2001+ Item {
2002+ id: spreadRow
2003+ // This width controls how much the spread can be flicked left/right. It's composed of:
2004+ // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
2005+ // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
2006+ width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
2007+
2008+ x: spreadView.contentX
2009+
2010+ Repeater {
2011+ id: spreadRepeater
2012+ model: ApplicationManager
2013+ delegate: TransformedSpreadDelegate {
2014+ id: appDelegate
2015+ objectName: "appDelegate" + index
2016+ startAngle: 45
2017+ endAngle: 5
2018+ startScale: 1.1
2019+ endScale: 0.7
2020+ startDistance: spreadView.tileDistance
2021+ endDistance: units.gu(.5)
2022+ width: spreadView.width
2023+ height: spreadView.height
2024+ selected: spreadView.selectedIndex == index
2025+ otherSelected: spreadView.selectedIndex >= 0 && !selected
2026+
2027+ z: index
2028+ x: index == 0 ? 0 : spreadView.width + (index - 1) * spreadView.tileDistance
2029+
2030+ // Each tile has a different progress value running from 0 to 1.
2031+ // A progress value of 0 means the tile is at the right edge. 1 means the tile has reched the left edge.
2032+ progress: {
2033+ var tileProgress = (spreadView.shiftedContentX - index * spreadView.tileDistance) / spreadView.width;
2034+ // Tile 1 needs to move directly from the beginning...
2035+ if (index == 1 && spreadView.phase < 2) {
2036+ tileProgress += spreadView.tileDistance / spreadView.width;
2037+ }
2038+ return tileProgress;
2039+ }
2040+
2041+ // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
2042+ animatedProgress: {
2043+ if (spreadView.phase == 0 && index < 2) {
2044+ if (progress < spreadView.positionMarker1) {
2045+ return progress;
2046+ } else if (progress < spreadView.positionMarker1 + snappingCurve.period){
2047+ return spreadView.positionMarker1 + snappingCurve.value * 3;
2048+ } else {
2049+ return spreadView.positionMarker2;
2050+ }
2051+ }
2052+ return progress;
2053+ }
2054+
2055+ EasingCurve {
2056+ id: snappingCurve
2057+ type: EasingCurve.OutQuad
2058+ period: 0.05
2059+ progress: appDelegate.progress - spreadView.positionMarker1
2060+ }
2061+
2062+ onClicked: {
2063+ if (spreadView.phase == 2) {
2064+ if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
2065+ spreadView.snapTo(index);
2066+ } else {
2067+ ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
2068+ }
2069+ }
2070+ }
2071+ }
2072+ }
2073+ }
2074+ }
2075+}
2076
2077=== renamed file 'qml/SideStage/SidestageHandle.qml' => 'qml/Stages/SidestageHandle.qml'
2078=== added file 'qml/Stages/SpreadDelegate.qml'
2079--- qml/Stages/SpreadDelegate.qml 1970-01-01 00:00:00 +0000
2080+++ qml/Stages/SpreadDelegate.qml 2014-04-01 18:39:46 +0000
2081@@ -0,0 +1,55 @@
2082+/*
2083+ * Copyright 2014 Canonical Ltd.
2084+ *
2085+ * This program is free software; you can redistribute it and/or modify
2086+ * it under the terms of the GNU Lesser General Public License as published by
2087+ * the Free Software Foundation; version 3.
2088+ *
2089+ * This program is distributed in the hope that it will be useful,
2090+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2091+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2092+ * GNU Lesser General Public License for more details.
2093+ *
2094+ * You should have received a copy of the GNU Lesser General Public License
2095+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2096+ *
2097+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
2098+*/
2099+
2100+import QtQuick 2.0
2101+
2102+Item {
2103+ id: root
2104+
2105+ signal clicked()
2106+
2107+ property real topMarginProgress
2108+
2109+ QtObject {
2110+ id: priv
2111+ property real heightDifference: root.height - appImage.implicitHeight
2112+ }
2113+
2114+ Image {
2115+ id: dropShadow
2116+ anchors.fill: appImage
2117+ anchors.margins: -units.gu(2)
2118+ source: "graphics/dropshadow.png"
2119+ opacity: .4
2120+ }
2121+ Image {
2122+ id: appImage
2123+ anchors {
2124+ left: parent.left;
2125+ bottom: parent.bottom;
2126+ top: parent.top;
2127+ topMargin: priv.heightDifference * Math.max(0, 1 - root.topMarginProgress)
2128+ }
2129+ source: model.screenshot
2130+ antialiasing: true
2131+ }
2132+ MouseArea {
2133+ anchors.fill: appImage
2134+ onClicked: root.clicked()
2135+ }
2136+}
2137
2138=== added file 'qml/Stages/StageWithSideStage.qml'
2139--- qml/Stages/StageWithSideStage.qml 1970-01-01 00:00:00 +0000
2140+++ qml/Stages/StageWithSideStage.qml 2014-04-01 18:39:46 +0000
2141@@ -0,0 +1,407 @@
2142+/*
2143+ * Copyright (C) 2014 Canonical, Ltd.
2144+ *
2145+ * This program is free software; you can redistribute it and/or modify
2146+ * it under the terms of the GNU General Public License as published by
2147+ * the Free Software Foundation; version 3.
2148+ *
2149+ * This program is distributed in the hope that it will be useful,
2150+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2151+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2152+ * GNU General Public License for more details.
2153+ *
2154+ * You should have received a copy of the GNU General Public License
2155+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2156+ */
2157+
2158+import QtQuick 2.0
2159+import Ubuntu.Components 0.1
2160+import "../Components"
2161+import Unity.Application 0.1
2162+import Ubuntu.Gestures 0.1
2163+
2164+Item {
2165+ id: root
2166+ objectName: "stages"
2167+ anchors.fill: parent
2168+
2169+ // Controls to be set from outside
2170+ property bool shown: false
2171+ property bool moving: false
2172+ property int dragAreaWidth
2173+
2174+ // State information propagated to the outside
2175+ readonly property bool painting: mainStageImage.visible || sideStageImage.visible || sideStageSnapAnimation.running
2176+ property bool fullscreen: priv.focusedApplication ? priv.focusedApplication.fullscreen : false
2177+ property bool overlayMode: (sideStageImage.shown && priv.mainStageAppId.length == 0) || priv.overlayOverride
2178+ || (priv.mainStageAppId.length == 0 && sideStageSnapAnimation.running)
2179+
2180+ readonly property int overlayWidth: priv.overlayOverride ? 0 : priv.sideStageWidth
2181+
2182+ onShownChanged: {
2183+ if (!shown) {
2184+ priv.mainStageAppId = "";
2185+ }
2186+ }
2187+
2188+ onMovingChanged: {
2189+ if (moving) {
2190+ if (!priv.mainStageAppId && !priv.sideStageAppId) {
2191+ // Pulling in from the right, make the last used (topmost) app visible
2192+ var application = ApplicationManager.get(0);
2193+ if (application.stage == ApplicationInfoInterface.SideStage) {
2194+ sideStageImage.application = application;
2195+ sideStageImage.x = root.width - sideStageImage.width
2196+ sideStageImage.visible = true;
2197+ } else {
2198+ mainStageImage.application = application;
2199+ mainStageImage.visible = true;
2200+ }
2201+ } else {
2202+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
2203+ if (priv.focusedApplicationId == priv.sideStageAppId) {
2204+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
2205+ }
2206+ }
2207+ } else {
2208+ mainStageImage.visible = false;
2209+ sideStageImage.visible = false;
2210+ }
2211+ }
2212+
2213+ QtObject {
2214+ id: priv
2215+
2216+ property int sideStageWidth: units.gu(40)
2217+
2218+
2219+ property string sideStageAppId
2220+ property string mainStageAppId
2221+
2222+
2223+ property var sideStageApp: ApplicationManager.findApplication(sideStageAppId)
2224+ property var mainStageApp: ApplicationManager.findApplication(mainStageAppId)
2225+
2226+ property string sideStageScreenshot: sideStageApp ? sideStageApp.screenshot : ""
2227+ property string mainStageScreenshot: mainStageApp ? mainStageApp.screenshot : ""
2228+
2229+ property string focusedApplicationId: ApplicationManager.focusedApplicationId
2230+ property var focusedApplication: ApplicationManager.findApplication(focusedApplicationId)
2231+ property url focusedScreenshot: focusedApplication ? focusedApplication.screenshot : ""
2232+
2233+ property bool waitingForMainScreenshot: false
2234+ property bool waitingForSideScreenshot: false
2235+ property bool waitingForScreenshots: waitingForMainScreenshot || waitingForSideScreenshot
2236+
2237+ property string startingAppId: ""
2238+
2239+ // Keep overlayMode even if there is no focused app (to allow pulling in the sidestage from the right)
2240+ property bool overlayOverride: false
2241+
2242+ onFocusedApplicationChanged: {
2243+ if (focusedApplication) {
2244+ if (focusedApplication.stage == ApplicationInfoInterface.MainStage) {
2245+ mainStageAppId = focusedApplicationId;
2246+ priv.overlayOverride = false;
2247+ if (priv.startingAppId == focusedApplicationId && sideStageImage.shown) {
2248+ // There was already a sidestage app on top. bring it back!
2249+ ApplicationManager.focusApplication(priv.sideStageAppId)
2250+ priv.startingAppId = "";
2251+ }
2252+ } else if (focusedApplication.stage == ApplicationInfoInterface.SideStage) {
2253+ sideStageAppId = focusedApplicationId;
2254+ if (priv.startingAppId == focusedApplicationId && !sideStageImage.shown) {
2255+ sideStageImage.snapToApp(focusedApplication);
2256+ priv.startingAppId = "";
2257+ }
2258+ }
2259+ } else if (root.overlayMode){
2260+ sideStageImage.snapTo(root.width)
2261+ }
2262+ }
2263+
2264+ onMainStageScreenshotChanged: {
2265+ waitingForMainScreenshot = false;
2266+ }
2267+
2268+ onSideStageScreenshotChanged: {
2269+ waitingForSideScreenshot = false;
2270+ }
2271+
2272+ onFocusedScreenshotChanged: {
2273+ waitingForSideScreenshot = false;
2274+ }
2275+
2276+ onWaitingForScreenshotsChanged: {
2277+ if (waitingForScreenshots) {
2278+ return;
2279+ }
2280+
2281+ if (root.moving) {
2282+ if (mainStageAppId) {
2283+ mainStageImage.application = mainStageApp
2284+ mainStageImage.visible = true;
2285+ }
2286+ if (sideStageAppId && focusedApplicationId == sideStageAppId) {
2287+ sideStageImage.application = sideStageApp;
2288+ sideStageImage.x = root.width - sideStageImage.width
2289+ sideStageImage.visible = true;
2290+ }
2291+ }
2292+ if (sideStageHandleMouseArea.pressed) {
2293+ if (sideStageAppId) {
2294+ sideStageImage.application = sideStageApp;
2295+ sideStageImage.x = root.width - sideStageImage.width
2296+ sideStageImage.visible = true;
2297+ }
2298+ if (mainStageAppId) {
2299+ mainStageImage.application = mainStageApp
2300+ mainStageImage.visible = true;
2301+ }
2302+ }
2303+ }
2304+
2305+ function requestNewScreenshot(stage) {
2306+ if (stage == ApplicationInfoInterface.MainStage && mainStageAppId) {
2307+ waitingForMainScreenshot = true;
2308+ ApplicationManager.updateScreenshot(mainStageAppId);
2309+ } else if (stage == ApplicationInfoInterface.SideStage && sideStageAppId) {
2310+ waitingForSideScreenshot = true;
2311+ ApplicationManager.updateScreenshot(sideStageAppId);
2312+ }
2313+ }
2314+
2315+ }
2316+ // FIXME: the signal connection seems to get lost with the fake application manager.
2317+ Connections {
2318+ target: priv.sideStageApp
2319+ onScreenshotChanged: priv.sideStageScreenshot = priv.sideStageApp.screenshot
2320+ }
2321+ Connections {
2322+ target: priv.mainStageApp
2323+ onScreenshotChanged: priv.mainStageScreenshot = priv.mainStageApp.screenshot
2324+ }
2325+
2326+ Connections {
2327+ target: ApplicationManager
2328+
2329+ onApplicationAdded: {
2330+ priv.startingAppId = appId;
2331+ splashScreenTimer.start();
2332+ var application = ApplicationManager.findApplication(appId)
2333+ if (application.stage == ApplicationInfoInterface.SideStage) {
2334+ sideStageSplash.visible = true;
2335+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
2336+ mainStageSplash.visible = true;
2337+ }
2338+ }
2339+
2340+ onFocusRequested: {
2341+ var application = ApplicationManager.findApplication(appId)
2342+ if (application.stage == ApplicationInfoInterface.SideStage) {
2343+ if (!root.shown) {
2344+ priv.mainStageAppId = "";
2345+ mainStageImage.application = null
2346+ }
2347+ if (sideStageImage.shown) {
2348+ sideStageImage.switchTo(application);
2349+ if (priv.mainStageAppId) {
2350+ mainStageImage.application = priv.mainStageApp;
2351+ mainStageImage.visible = true;
2352+ }
2353+ } else {
2354+ sideStageImage.application = application;
2355+ sideStageImage.snapToApp(application);
2356+ }
2357+ } else if (application.stage == ApplicationInfoInterface.MainStage) {
2358+ if (root.shown) {
2359+ if (sideStageImage.shown) {
2360+ sideStageImage.application = priv.sideStageApp;
2361+ sideStageImage.visible = true;
2362+ }
2363+ priv.mainStageAppId = application.appId;
2364+ mainStageImage.switchTo(application)
2365+ ApplicationManager.focusApplication(appId)
2366+ if (sideStageImage.shown) {
2367+ // There was already a focused SS app. Bring it back
2368+ ApplicationManager.focusApplication(priv.sideStageAppId)
2369+ }
2370+ } else {
2371+ if (sideStageImage.shown) {
2372+ sideStageImage.visible = false;
2373+ sideStageImage.x = root.width;
2374+ }
2375+
2376+ mainStageImage.application = application;
2377+ ApplicationManager.focusApplication(appId)
2378+ }
2379+ }
2380+ }
2381+
2382+ onApplicationRemoved: {
2383+ if (priv.mainStageAppId == appId) {
2384+ priv.mainStageAppId = "";
2385+ }
2386+ if (priv.sideStageAppId == appId) {
2387+ priv.sideStageAppId = "";
2388+ }
2389+ if (priv.sideStageAppId.length == 0) {
2390+ sideStageImage.shown = false;
2391+ priv.overlayOverride = false;
2392+ }
2393+ }
2394+
2395+ }
2396+
2397+ Timer {
2398+ id: splashScreenTimer
2399+ // FIXME: apart from removing this completely in the future and make the app surface paint
2400+ // meaningful stuff, also check for colin's stuff to land so we can shape 1.4 secs away from here
2401+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-manifest/+merge/210520
2402+ // https://code.launchpad.net/~cjwatson/upstart-app-launch/libclick-pkgdir/+merge/209909
2403+ interval: 1700
2404+ repeat: false
2405+ onTriggered: {
2406+ mainStageSplash.visible = false;
2407+ sideStageSplash.visible = false;
2408+ }
2409+ }
2410+
2411+ SwitchingApplicationImage {
2412+ id: mainStageImage
2413+ anchors.bottom: parent.bottom
2414+ width: parent.width
2415+ visible: false
2416+
2417+ onSwitched: {
2418+ sideStageImage.visible = false;
2419+ }
2420+ }
2421+
2422+ Rectangle {
2423+ id: mainStageSplash
2424+ anchors.fill: root
2425+ anchors.rightMargin: root.width - sideStageImage.x
2426+ color: "white"
2427+ }
2428+
2429+ SidestageHandle {
2430+ id: sideStageHandle
2431+ anchors { top: parent.top; right: sideStageImage.left; bottom: parent.bottom }
2432+ width: root.dragAreaWidth
2433+ visible: root.shown && priv.sideStageAppId && sideStageImage.x < root.width
2434+
2435+ }
2436+ MouseArea {
2437+ id: sideStageHandleMouseArea
2438+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: sideStageImage.shown ? sideStageImage.width : 0}
2439+ width: root.dragAreaWidth
2440+ visible: priv.sideStageAppId
2441+
2442+ property var dragPoints: new Array()
2443+
2444+ onPressed: {
2445+ priv.requestNewScreenshot(ApplicationInfoInterface.SideStage)
2446+ if (priv.mainStageAppId) {
2447+ priv.requestNewScreenshot(ApplicationInfoInterface.MainStage)
2448+ }
2449+ }
2450+
2451+ onMouseXChanged: {
2452+ dragPoints.push(mouseX)
2453+
2454+ var dragPoint = root.width + mouseX;
2455+ if (sideStageImage.shown) {
2456+ dragPoint -= sideStageImage.width
2457+ }
2458+ sideStageImage.x = Math.max(root.width - sideStageImage.width, dragPoint)
2459+ }
2460+
2461+ onReleased: {
2462+ var distance = 0;
2463+ var lastX = dragPoints[0];
2464+ var oneWayFlick = true;
2465+ for (var i = 0; i < dragPoints.length; ++i) {
2466+ if (dragPoints[i] < lastX) {
2467+ oneWayFlick = false;
2468+ }
2469+ distance += dragPoints[i] - lastX;
2470+ lastX = dragPoints[i];
2471+ }
2472+ dragPoints = [];
2473+
2474+ if (oneWayFlick || distance > sideStageImage.width / 2) {
2475+ sideStageImage.snapTo(root.width)
2476+ } else {
2477+ sideStageImage.snapToApp(priv.sideStageApp)
2478+ }
2479+ }
2480+ }
2481+
2482+ SwitchingApplicationImage {
2483+ id: sideStageImage
2484+ width: priv.sideStageWidth
2485+ height: root.height
2486+ x: root.width
2487+ anchors.bottom: parent.bottom
2488+ visible: true
2489+ property bool shown: false
2490+
2491+ onSwitched: {
2492+ mainStageImage.visible = false;
2493+ ApplicationManager.focusApplication(application.appId)
2494+ }
2495+
2496+ function snapTo(targetX) {
2497+ sideStageSnapAnimation.targetX = targetX
2498+ sideStageImage.visible = true;
2499+ if (priv.mainStageAppId) {
2500+ mainStageImage.application = priv.mainStageApp
2501+ mainStageImage.visible = true;
2502+ }
2503+ sideStageSnapAnimation.start();
2504+ }
2505+
2506+ function snapToApp(application) {
2507+ sideStageImage.application = application
2508+ sideStageSnapAnimation.snapToId = application.appId;
2509+ snapTo(root.width - sideStageImage.width);
2510+ }
2511+
2512+ SequentialAnimation {
2513+ id: sideStageSnapAnimation
2514+ property int targetX: root.width
2515+ property string snapToId
2516+
2517+ UbuntuNumberAnimation { target: sideStageImage; property: "x"; to: sideStageSnapAnimation.targetX; duration: UbuntuAnimation.SlowDuration }
2518+ ScriptAction {
2519+ script: {
2520+ if (sideStageSnapAnimation.targetX == root.width) {
2521+ if (priv.mainStageAppId) {
2522+ ApplicationManager.focusApplication(priv.mainStageAppId)
2523+ } else {
2524+ priv.overlayOverride = true;
2525+ ApplicationManager.unfocusCurrentApplication();
2526+ }
2527+ sideStageImage.shown = false;
2528+ }
2529+ if (sideStageSnapAnimation.snapToId) {
2530+ ApplicationManager.focusApplication(sideStageSnapAnimation.snapToId)
2531+ sideStageSnapAnimation.snapToId = "";
2532+ sideStageImage.shown = true;
2533+ priv.overlayOverride = false;
2534+ }
2535+ sideStageImage.visible = false;
2536+ mainStageImage.visible = false;
2537+ }
2538+ }
2539+ }
2540+ }
2541+
2542+ Rectangle {
2543+ id: sideStageSplash
2544+ anchors.fill: parent
2545+ anchors.leftMargin: sideStageImage.x
2546+ color: "white"
2547+ }
2548+}
2549
2550=== added file 'qml/Stages/SwitchingApplicationImage.qml'
2551--- qml/Stages/SwitchingApplicationImage.qml 1970-01-01 00:00:00 +0000
2552+++ qml/Stages/SwitchingApplicationImage.qml 2014-04-01 18:39:46 +0000
2553@@ -0,0 +1,81 @@
2554+/*
2555+ * Copyright 2014 Canonical Ltd.
2556+ *
2557+ * This program is free software; you can redistribute it and/or modify
2558+ * it under the terms of the GNU Lesser General Public License as published by
2559+ * the Free Software Foundation; version 3.
2560+ *
2561+ * This program is distributed in the hope that it will be useful,
2562+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2563+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2564+ * GNU Lesser General Public License for more details.
2565+ *
2566+ * You should have received a copy of the GNU Lesser General Public License
2567+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2568+ *
2569+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
2570+*/
2571+
2572+import QtQuick 2.0
2573+import Ubuntu.Components 0.1
2574+
2575+Rectangle {
2576+ id: root
2577+ implicitHeight: image.implicitHeight
2578+ implicitWidth: image.implicitWidth
2579+ color: "black"
2580+
2581+ property var application
2582+
2583+ signal switched()
2584+
2585+ function switchTo(application) {
2586+ if (root.application == application) {
2587+ root.switched();
2588+ return;
2589+ }
2590+
2591+ priv.newApplication = application
2592+ root.visible = true;
2593+ switchToAnimation.start()
2594+ }
2595+
2596+ QtObject {
2597+ id: priv
2598+ property var newApplication
2599+ }
2600+
2601+ Image {
2602+ id: newImage
2603+ anchors.bottom: parent.bottom
2604+ width: root.width
2605+ source: priv.newApplication ? priv.newApplication.screenshot : ""
2606+ }
2607+
2608+ Image {
2609+ id: image
2610+ visible: true
2611+ source: root.application ? root.application.screenshot : ""
2612+ width: root.width
2613+ height: sourceSize.height
2614+ anchors.bottom: parent.bottom
2615+
2616+ }
2617+
2618+ SequentialAnimation {
2619+ id: switchToAnimation
2620+ ParallelAnimation {
2621+ UbuntuNumberAnimation { target: image; property: "x"; from: 0; to: root.width; duration: UbuntuAnimation.SlowDuration }
2622+ UbuntuNumberAnimation { target: newImage; property: "scale"; from: 0.7; to: 1; duration: UbuntuAnimation.SlowDuration }
2623+ }
2624+ ScriptAction {
2625+ script: {
2626+ image.x = 0
2627+ root.application = priv.newApplication
2628+ root.visible = false;
2629+ priv.newApplication = null
2630+ root.switched();
2631+ }
2632+ }
2633+ }
2634+}
2635
2636=== added file 'qml/Stages/TransformedSpreadDelegate.qml'
2637--- qml/Stages/TransformedSpreadDelegate.qml 1970-01-01 00:00:00 +0000
2638+++ qml/Stages/TransformedSpreadDelegate.qml 2014-04-01 18:39:46 +0000
2639@@ -0,0 +1,327 @@
2640+/*
2641+ * Copyright 2014 Canonical Ltd.
2642+ *
2643+ * This program is free software; you can redistribute it and/or modify
2644+ * it under the terms of the GNU Lesser General Public License as published by
2645+ * the Free Software Foundation; version 3.
2646+ *
2647+ * This program is distributed in the hope that it will be useful,
2648+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2649+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2650+ * GNU Lesser General Public License for more details.
2651+ *
2652+ * You should have received a copy of the GNU Lesser General Public License
2653+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2654+ *
2655+ * Authors: Michael Zanetti <michael.zanetti@canonical.com>
2656+*/
2657+
2658+import QtQuick 2.0
2659+import Utils 0.1
2660+import Ubuntu.Components 0.1
2661+
2662+SpreadDelegate {
2663+ id: root
2664+
2665+ property bool selected: false
2666+ property bool otherSelected: false
2667+
2668+ // The progress animates the tiles. A value > 0 makes it appear from the right edge. At 1 it reaches the end position.
2669+ property real progress: 0
2670+ // This is required to snap tile 1 during phase 1 and 2.
2671+ property real animatedProgress: 0
2672+
2673+ property real startAngle: 0
2674+ property real endAngle: 0
2675+
2676+ property real startScale: 1
2677+ property real endScale: 1
2678+
2679+ // Specific to just one tile
2680+ property real tile1StartScale: startScale + .4
2681+ property real tile0SnapAngle: 10
2682+
2683+ property real startDistance: units.gu(5)
2684+ property real endDistance: units.gu(.5)
2685+
2686+ onSelectedChanged: {
2687+ if (selected) {
2688+ priv.snapshot();
2689+ }
2690+ priv.isSelected = selected;
2691+ }
2692+
2693+ onOtherSelectedChanged: {
2694+ if (otherSelected) {
2695+ priv.snapshot();
2696+ }
2697+ priv.otherSelected = otherSelected;
2698+ }
2699+
2700+ Connections {
2701+ target: spreadView
2702+ onPhaseChanged: {
2703+ if (spreadView.phase == 1) {
2704+ if (index == 0) {
2705+ priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
2706+ priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
2707+ priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
2708+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, 1, spreadView.positionMarker4);
2709+ } else if (index == 1) {
2710+ // find where the main easing for Tile 1 would be when reaching phase 2
2711+ var phase2Progress = spreadView.positionMarker4 - spreadView.tileDistance / spreadView.width;
2712+ priv.phase2startTranslate = priv.easingAnimation(0, phase2Progress, 0, -spreadView.width + root.endDistance, phase2Progress);
2713+ priv.phase2startAngle = priv.easingAnimation(0, phase2Progress, root.startAngle, root.endAngle, phase2Progress);
2714+ priv.phase2startScale = priv.easingAnimation(0, phase2Progress, root.startScale, root.endScale, phase2Progress);
2715+ priv.phase2startTopMarginProgress = priv.easingAnimation(0, 1, 0, spreadView.positionMarker4, phase2Progress);
2716+ }
2717+ }
2718+ }
2719+ }
2720+
2721+ QtObject {
2722+ id: priv
2723+ property bool isSelected: false
2724+ property bool otherSelected: false
2725+ property real selectedProgress
2726+ property real selectedXTranslate
2727+ property real selectedAngle
2728+ property real selectedScale
2729+ property real selectedOpacity
2730+ property real selectedTopMarginProgress
2731+
2732+ // Those values are needed as target values for the end of phase 1.
2733+ // As they are static values, lets calculate them once when entering phase 1 instead of calculating them in each animation pass.
2734+ property real phase2startTranslate
2735+ property real phase2startAngle
2736+ property real phase2startScale
2737+ property real phase2startTopMarginProgress
2738+
2739+ function snapshot() {
2740+ selectedProgress = root.progress;
2741+ selectedXTranslate = xTranslate;
2742+ selectedAngle = angle;
2743+ selectedScale = scale;
2744+ selectedOpacity = opacity;
2745+ selectedTopMarginProgress = topMarginProgress;
2746+ }
2747+
2748+ // This calculates how much negative progress there can be if unwinding the spread completely
2749+ // the progress for each tile starts at 0 when it crosses the right edge, so the later a tile comes in,
2750+ // the bigger its negativeProgress can be.
2751+ property real negativeProgress: {
2752+ if (index == 1 && spreadView.phase < 2) {
2753+ return 0;
2754+ }
2755+ return -index * spreadView.tileDistance / spreadView.width;
2756+ }
2757+
2758+ function linearAnimation(startProgress, endProgress, startValue, endValue, progress) {
2759+ // progress : progressDiff = value : valueDiff => value = progress * valueDiff / progressDiff
2760+ return (progress - startProgress) * (endValue - startValue) / (endProgress - startProgress) + startValue;
2761+ }
2762+
2763+ function easingAnimation(startProgress, endProgress, startValue, endValue, progress) {
2764+ helperEasingCurve.progress = progress - startProgress;
2765+ helperEasingCurve.period = endProgress - startProgress;
2766+ return helperEasingCurve.value * (endValue - startValue) + startValue;
2767+ }
2768+
2769+ property real animatedEndDistance: linearAnimation(0, 2, root.endDistance, 0, root.progress)
2770+
2771+ // The following blocks handle the animation of the tile in the spread.
2772+ // At the beginning, each tile is attached at the right edge, outside the screen.
2773+ // The progress for each tile starts at 0 and it reaches its end position at a progress of 1.
2774+ // The first phases are handled special for the first 2 tiles. as we do the alt-tab and snapping in there
2775+ // Once we reached phase 3, the animation is the same for all tiles.
2776+ // When a tile is selected, the animation state is snapshotted, and the spreadView is unwound (progress animates
2777+ // back to negativeProgress). All tiles are kept in place and faded out to 0 opacity except
2778+ // the selected tile, which is animated from the snapshotted position to be fullscreen.
2779+
2780+ property real xTranslate: {
2781+ if (otherSelected) {
2782+ if (spreadView.phase < 2 && index == 0) {
2783+ return linearAnimation(selectedProgress, negativeProgress,
2784+ selectedXTranslate, selectedXTranslate - spreadView.tileDistance, root.progress);
2785+ }
2786+
2787+ return selectedXTranslate;
2788+ }
2789+
2790+ switch (index) {
2791+ case 0:
2792+ if (spreadView.phase == 0) {
2793+ return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
2794+ 0, -spreadView.width * .25, root.animatedProgress));
2795+ } else if (spreadView.phase == 1){
2796+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2797+ -spreadView.width * .25, priv.phase2startTranslate, root.progress);
2798+ } else if (!priv.isSelected){ // phase 2
2799+ // Apply the same animation as with the rest but add spreadView.width to align it with the others.
2800+ return -easingCurve.value * spreadView.width + spreadView.width;
2801+ } else if (priv.isSelected) {
2802+ return linearAnimation(selectedProgress, negativeProgress, selectedXTranslate, 0, root.progress);
2803+ }
2804+
2805+ case 1:
2806+ if (spreadView.phase == 0 && !priv.isSelected) {
2807+ return linearAnimation(0, spreadView.positionMarker2,
2808+ 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
2809+ } else if (spreadView.phase == 1 && !priv.isSelected) {
2810+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2811+ -spreadView.width * spreadView.snapPosition, priv.phase2startTranslate,
2812+ root.progress);
2813+ }
2814+ }
2815+
2816+ if (priv.isSelected) {
2817+ // Distance to left edge
2818+ var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
2819+ return linearAnimation(selectedProgress, negativeProgress,
2820+ selectedXTranslate, targetTranslate, root.progress);
2821+ }
2822+
2823+ // Fix it at the right edge...
2824+ var rightEdgeOffset = -((index - 1) * root.startDistance);
2825+ // ...and use our easing to move them to the left. Stop a bit earlier for each tile
2826+ return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
2827+
2828+ }
2829+
2830+ property real angle: {
2831+ if (priv.otherSelected) {
2832+ return priv.selectedAngle;
2833+ }
2834+ if (priv.isSelected) {
2835+ return linearAnimation(selectedProgress, negativeProgress, selectedAngle, 0, root.progress);
2836+ }
2837+ switch (index) {
2838+ case 0:
2839+ if (spreadView.phase == 0) {
2840+ return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
2841+ 0, root.tile0SnapAngle, root.animatedProgress));
2842+ } else if (spreadView.phase == 1) {
2843+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2844+ root.tile0SnapAngle, phase2startAngle, root.progress);
2845+ }
2846+ case 1:
2847+ if (spreadView.phase == 0) {
2848+ return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
2849+ root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
2850+ } else if (spreadView.phase == 1) {
2851+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2852+ root.startAngle * (1-spreadView.snapPosition), priv.phase2startAngle,
2853+ root.progress);
2854+ }
2855+ }
2856+ return root.startAngle - easingCurve.value * (root.startAngle - root.endAngle);
2857+ }
2858+
2859+ property real scale: {
2860+ if (priv.otherSelected) {
2861+ return priv.selectedScale;
2862+ }
2863+ if (priv.isSelected) {
2864+ return linearAnimation(selectedProgress, negativeProgress, selectedScale, 1, root.progress);
2865+ }
2866+
2867+ switch (index) {
2868+ case 0:
2869+ if (spreadView.phase == 0) {
2870+ return 1;
2871+ } else if (spreadView.phase == 1) {
2872+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2873+ 1, phase2startScale, root.progress);
2874+ }
2875+ case 1:
2876+ if (spreadView.phase == 0) {
2877+ var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
2878+ return linearAnimation(0, spreadView.positionMarker2,
2879+ root.tile1StartScale, targetScale, root.animatedProgress);
2880+ } else if (spreadView.phase == 1) {
2881+ var startScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
2882+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2883+ startScale, priv.phase2startScale, root.progress);
2884+ }
2885+ }
2886+ return root.startScale - easingCurve.value * (root.startScale - root.endScale);
2887+ }
2888+
2889+ property real opacity: {
2890+ if (priv.otherSelected) {
2891+ return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
2892+ selectedOpacity, 0, root.progress);
2893+ }
2894+ if (index == 0) {
2895+ switch (spreadView.phase) {
2896+ case 0:
2897+ return linearAnimation(0, spreadView.positionMarker2, 1, .3, root.animatedProgress);
2898+ case 1:
2899+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2900+ .3, 1, root.animatedProgress);
2901+ }
2902+ }
2903+
2904+ return 1;
2905+ }
2906+
2907+ property real topMarginProgress: {
2908+ if (priv.isSelected) {
2909+ return linearAnimation(selectedProgress, negativeProgress, selectedTopMarginProgress, 0, root.progress);
2910+ }
2911+
2912+ switch (index) {
2913+ case 0:
2914+ if (spreadView.phase == 0) {
2915+ return 0;
2916+ } else if (spreadView.phase == 1) {
2917+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2918+ 0, priv.phase2startTopMarginProgress, root.progress);
2919+ }
2920+ break;
2921+ case 1:
2922+ if (spreadView.phase == 0) {
2923+ return 0;
2924+ } else if (spreadView.phase == 1) {
2925+ return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
2926+ 0, priv.phase2startTopMarginProgress, root.progress);
2927+ }
2928+ }
2929+
2930+ return easingCurve.value;
2931+ }
2932+ }
2933+
2934+ transform: [
2935+ Rotation {
2936+ origin { x: 0; y: spreadView.height / 2 }
2937+ axis { x: 0; y: 1; z: 0 }
2938+ angle: priv.angle
2939+ },
2940+ Scale {
2941+ origin { x: 0; y: spreadView.height / 2 }
2942+ xScale: priv.scale
2943+ yScale: xScale
2944+ },
2945+ Translate {
2946+ x: priv.xTranslate
2947+ }
2948+ ]
2949+ opacity: priv.opacity
2950+ topMarginProgress: priv.topMarginProgress
2951+
2952+ EasingCurve {
2953+ id: easingCurve
2954+ type: EasingCurve.OutSine
2955+ period: 1 - spreadView.positionMarker2
2956+ progress: root.animatedProgress
2957+ }
2958+
2959+ // This is used as a calculation helper to figure values for progress other than the current one
2960+ // Do not bind anything to this...
2961+ EasingCurve {
2962+ id: helperEasingCurve
2963+ type: easingCurve.type
2964+ period: easingCurve.period
2965+ }
2966+}
2967
2968=== added directory 'qml/Stages/graphics'
2969=== added file 'qml/Stages/graphics/dropshadow.png'
2970Binary files qml/Stages/graphics/dropshadow.png 1970-01-01 00:00:00 +0000 and qml/Stages/graphics/dropshadow.png 2014-04-01 18:39:46 +0000 differ
2971=== renamed file 'qml/SideStage/graphics/sidestage_handle@20.png' => 'qml/Stages/graphics/sidestage_handle@20.png'
2972=== renamed file 'qml/SideStage/graphics/sidestage_handle@20.sci' => 'qml/Stages/graphics/sidestage_handle@20.sci'
2973=== modified file 'tests/autopilot/unity8/shell/emulators/dash.py'
2974--- tests/autopilot/unity8/shell/emulators/dash.py 2014-03-17 10:42:57 +0000
2975+++ tests/autopilot/unity8/shell/emulators/dash.py 2014-04-01 18:39:46 +0000
2976@@ -80,10 +80,7 @@
2977 'No scope found with id {0}'.format(scope_id))
2978
2979 def _get_scope_from_loader(self, loader):
2980- if loader.scopeId == 'clickscope':
2981- return loader.select_single(DashApps)
2982- else:
2983- return loader.select_single(GenericScopeView)
2984+ return loader.get_children()[0]
2985
2986 def _open_scope_scrolling(self, scope_loader):
2987 scroll = self._get_scroll_direction(scope_loader)
2988
2989=== modified file 'tests/autopilot/unity8/shell/emulators/main_window.py'
2990--- tests/autopilot/unity8/shell/emulators/main_window.py 2014-03-02 04:44:30 +0000
2991+++ tests/autopilot/unity8/shell/emulators/main_window.py 2014-04-01 18:39:46 +0000
2992@@ -112,4 +112,4 @@
2993
2994 def get_current_focused_app_id(self):
2995 """Return the id of the focused application."""
2996- return self.select_single('Shell').currentFocusedAppId
2997+ return self.select_single('Shell').focusedApplicationId
2998
2999=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.cpp'
3000--- tests/mocks/Unity/Application/ApplicationInfo.cpp 2013-09-05 12:21:35 +0000
3001+++ tests/mocks/Unity/Application/ApplicationInfo.cpp 2014-04-01 18:39:46 +0000
3002@@ -20,6 +20,7 @@
3003 #include <QQuickItem>
3004 #include <QQuickView>
3005 #include <QQmlComponent>
3006+#include <QTimer>
3007
3008 ApplicationInfo::ApplicationInfo(const QString &appId, QObject *parent)
3009 : ApplicationInfoInterface(appId, parent)
3010@@ -32,6 +33,7 @@
3011 ,m_windowComponent(0)
3012 ,m_parentItem(0)
3013 {
3014+ QTimer::singleShot(300, this, SLOT(setRunning()));
3015 }
3016
3017 ApplicationInfo::ApplicationInfo(QObject *parent)
3018@@ -44,6 +46,7 @@
3019 ,m_windowComponent(0)
3020 ,m_parentItem(0)
3021 {
3022+ QTimer::singleShot(300, this, SLOT(setRunning()));
3023 }
3024
3025 void ApplicationInfo::onWindowComponentStatusChanged(QQmlComponent::Status status)
3026@@ -52,6 +55,12 @@
3027 doCreateWindowItem();
3028 }
3029
3030+void ApplicationInfo::setRunning()
3031+{
3032+ m_state = Running;
3033+ Q_EMIT stateChanged();
3034+}
3035+
3036 void ApplicationInfo::createWindowComponent()
3037 {
3038 // The assumptions I make here really should hold.
3039
3040=== modified file 'tests/mocks/Unity/Application/ApplicationInfo.h'
3041--- tests/mocks/Unity/Application/ApplicationInfo.h 2013-10-11 11:37:04 +0000
3042+++ tests/mocks/Unity/Application/ApplicationInfo.h 2014-04-01 18:39:46 +0000
3043@@ -74,6 +74,7 @@
3044 IMPLEMENT_PROPERTY(fullscreen, Fullscreen, bool)
3045 IMPLEMENT_PROPERTY(imageQml, ImageQml, QString)
3046 IMPLEMENT_PROPERTY(windowQml, WindowQml, QString)
3047+ IMPLEMENT_PROPERTY(screenshot, Screenshot, QUrl)
3048
3049 #undef IMPLEMENT_PROPERTY
3050
3051@@ -83,6 +84,7 @@
3052
3053 private Q_SLOTS:
3054 void onWindowComponentStatusChanged(QQmlComponent::Status status);
3055+ void setRunning();
3056
3057 private:
3058 void createWindowItem();
3059
3060=== modified file 'tests/mocks/Unity/Application/ApplicationManager.cpp'
3061--- tests/mocks/Unity/Application/ApplicationManager.cpp 2013-12-17 16:04:47 +0000
3062+++ tests/mocks/Unity/Application/ApplicationManager.cpp 2014-04-01 18:39:46 +0000
3063@@ -24,13 +24,17 @@
3064 #include <QQuickItem>
3065 #include <QQuickView>
3066 #include <QQmlComponent>
3067+#include <QTimer>
3068+#include <QDateTime>
3069
3070 ApplicationManager::ApplicationManager(QObject *parent)
3071 : ApplicationManagerInterface(parent)
3072+ , m_suspended(false)
3073 , m_mainStageComponent(0)
3074 , m_mainStage(0)
3075 , m_sideStageComponent(0)
3076 , m_sideStage(0)
3077+ , m_rightMargin(0)
3078 {
3079 buildListOfAvailableApplications();
3080 }
3081@@ -63,6 +67,8 @@
3082 return app->state();
3083 case RoleFocused:
3084 return app->focused();
3085+ case RoleScreenshot:
3086+ return app->screenshot();
3087 default:
3088 return QVariant();
3089 }
3090@@ -84,13 +90,16 @@
3091 }
3092
3093 void ApplicationManager::add(ApplicationInfo *application) {
3094- if (!application)
3095+ if (!application) {
3096 return;
3097+ }
3098
3099 beginInsertRows(QModelIndex(), m_runningApplications.size(), m_runningApplications.size());
3100 m_runningApplications.append(application);
3101 endInsertRows();
3102+ Q_EMIT applicationAdded(application->appId());
3103 Q_EMIT countChanged();
3104+ Q_EMIT focusRequested(application->appId());
3105 }
3106
3107 void ApplicationManager::remove(ApplicationInfo *application) {
3108@@ -99,6 +108,7 @@
3109 beginRemoveRows(QModelIndex(), i, i);
3110 m_runningApplications.removeAt(i);
3111 endRemoveRows();
3112+ Q_EMIT applicationRemoved(application->appId());
3113 Q_EMIT countChanged();
3114 }
3115 }
3116@@ -171,6 +181,8 @@
3117 }
3118 add(application);
3119
3120+ QMetaObject::invokeMethod(this, "focusApplication", Qt::QueuedConnection, Q_ARG(QString, appId));
3121+
3122 return application;
3123 }
3124
3125@@ -180,8 +192,32 @@
3126 if (application == nullptr)
3127 return false;
3128
3129+ if (application->appId() == focusedApplicationId()) {
3130+ unfocusCurrentApplication();
3131+ }
3132 remove(application);
3133- Q_EMIT focusedApplicationIdChanged();
3134+ return true;
3135+}
3136+
3137+bool ApplicationManager::updateScreenshot(const QString &appId)
3138+{
3139+ int idx = -1;
3140+ ApplicationInfo *application = nullptr;
3141+ for (int i = 0; i < m_availableApplications.count(); ++i) {
3142+ application = m_availableApplications.at(i);
3143+ if (application->appId() == appId) {
3144+ idx = i;
3145+ break;
3146+ }
3147+ }
3148+
3149+ if (idx == -1) {
3150+ return false;
3151+ }
3152+
3153+ application->setScreenshot(QString("image://application/%1/%2").arg(appId).arg(QDateTime::currentMSecsSinceEpoch()));
3154+ QModelIndex appIndex = index(idx);
3155+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
3156 return true;
3157 }
3158
3159@@ -194,6 +230,25 @@
3160 return QString();
3161 }
3162
3163+bool ApplicationManager::suspended() const
3164+{
3165+ return m_suspended;
3166+}
3167+
3168+void ApplicationManager::setSuspended(bool suspended)
3169+{
3170+ ApplicationInfo *focusedApp = findApplication(focusedApplicationId());
3171+ if (focusedApp) {
3172+ if (suspended) {
3173+ focusedApp->setState(ApplicationInfo::Suspended);
3174+ } else {
3175+ focusedApp->setState(ApplicationInfo::Running);
3176+ }
3177+ }
3178+ m_suspended = suspended;
3179+ Q_EMIT suspendedChanged();
3180+}
3181+
3182 bool ApplicationManager::focusApplication(const QString &appId)
3183 {
3184 ApplicationInfo *application = findApplication(appId);
3185@@ -242,6 +297,15 @@
3186 return true;
3187 }
3188
3189+bool ApplicationManager::requestFocusApplication(const QString &appId)
3190+{
3191+ if (appId != focusedApplicationId()) {
3192+ QMetaObject::invokeMethod(this, "focusRequested", Qt::QueuedConnection, Q_ARG(QString, appId));
3193+ return true;
3194+ }
3195+ return false;
3196+}
3197+
3198 void ApplicationManager::unfocusCurrentApplication()
3199 {
3200 for (ApplicationInfo *app : m_runningApplications) {
3201@@ -270,10 +334,12 @@
3202 "Image {\n"
3203 " anchors.fill: parent\n"
3204 " anchors.topMargin: %1\n"
3205- " source: \"file://%2/Dash/graphics/phone/screenshots/%3.png\"\n"
3206+ " anchors.rightMargin: %2\n"
3207+ " source: \"file://%3/Dash/graphics/phone/screenshots/%4.png\"\n"
3208 " smooth: true\n"
3209 " fillMode: Image.PreserveAspectCrop\n"
3210 "}").arg(topMargin)
3211+ .arg(m_rightMargin)
3212 .arg(qmlDirectory())
3213 .arg(application->icon().toString());
3214 application->setWindowQml(windowQml);
3215@@ -299,6 +365,7 @@
3216 application->setName("Phone");
3217 application->setIcon(QUrl("phone"));
3218 application->setStage(ApplicationInfo::SideStage);
3219+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3220 generateQmlStrings(application);
3221 m_availableApplications.append(application);
3222
3223@@ -307,6 +374,7 @@
3224 application->setName("Camera");
3225 application->setIcon(QUrl("camera"));
3226 application->setFullscreen(true);
3227+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3228 generateQmlStrings(application);
3229 m_availableApplications.append(application);
3230
3231@@ -314,6 +382,7 @@
3232 application->setAppId("gallery-app");
3233 application->setName("Gallery");
3234 application->setIcon(QUrl("gallery"));
3235+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3236 generateQmlStrings(application);
3237 m_availableApplications.append(application);
3238
3239@@ -322,13 +391,16 @@
3240 application->setName("Facebook");
3241 application->setIcon(QUrl("facebook"));
3242 application->setStage(ApplicationInfo::SideStage);
3243+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3244 generateQmlStrings(application);
3245 m_availableApplications.append(application);
3246
3247 application = new ApplicationInfo(this);
3248 application->setAppId("webbrowser-app");
3249+ application->setFullscreen(true);
3250 application->setName("Browser");
3251 application->setIcon(QUrl("browser"));
3252+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3253 generateQmlStrings(application);
3254 m_availableApplications.append(application);
3255
3256@@ -337,6 +409,7 @@
3257 application->setName("Twitter");
3258 application->setIcon(QUrl("twitter"));
3259 application->setStage(ApplicationInfo::SideStage);
3260+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3261 generateQmlStrings(application);
3262 m_availableApplications.append(application);
3263
3264@@ -344,6 +417,7 @@
3265 application->setAppId("gmail-webapp");
3266 application->setName("GMail");
3267 application->setIcon(QUrl("gmail"));
3268+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3269 m_availableApplications.append(application);
3270
3271 application = new ApplicationInfo(this);
3272@@ -351,6 +425,7 @@
3273 application->setName("Weather");
3274 application->setIcon(QUrl("weather"));
3275 application->setStage(ApplicationInfo::SideStage);
3276+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3277 generateQmlStrings(application);
3278 m_availableApplications.append(application);
3279
3280@@ -359,6 +434,7 @@
3281 application->setName("Notepad");
3282 application->setIcon(QUrl("notepad"));
3283 application->setStage(ApplicationInfo::SideStage);
3284+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3285 m_availableApplications.append(application);
3286
3287 application = new ApplicationInfo(this);
3288@@ -366,6 +442,7 @@
3289 application->setName("Calendar");
3290 application->setIcon(QUrl("calendar"));
3291 application->setStage(ApplicationInfo::SideStage);
3292+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3293 m_availableApplications.append(application);
3294
3295 application = new ApplicationInfo(this);
3296@@ -373,18 +450,21 @@
3297 application->setName("Media Player");
3298 application->setIcon(QUrl("mediaplayer-app"));
3299 application->setFullscreen(true);
3300+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3301 m_availableApplications.append(application);
3302
3303 application = new ApplicationInfo(this);
3304 application->setAppId("evernote");
3305 application->setName("Evernote");
3306 application->setIcon(QUrl("evernote"));
3307+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3308 m_availableApplications.append(application);
3309
3310 application = new ApplicationInfo(this);
3311 application->setAppId("map");
3312 application->setName("Map");
3313 application->setIcon(QUrl("map"));
3314+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3315 generateQmlStrings(application);
3316 m_availableApplications.append(application);
3317
3318@@ -392,24 +472,28 @@
3319 application->setAppId("pinterest");
3320 application->setName("Pinterest");
3321 application->setIcon(QUrl("pinterest"));
3322+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3323 m_availableApplications.append(application);
3324
3325 application = new ApplicationInfo(this);
3326 application->setAppId("soundcloud");
3327 application->setName("SoundCloud");
3328 application->setIcon(QUrl("soundcloud"));
3329+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3330 m_availableApplications.append(application);
3331
3332 application = new ApplicationInfo(this);
3333 application->setAppId("wikipedia");
3334 application->setName("Wikipedia");
3335 application->setIcon(QUrl("wikipedia"));
3336+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3337 m_availableApplications.append(application);
3338
3339 application = new ApplicationInfo(this);
3340 application->setAppId("youtube");
3341 application->setName("YouTube");
3342 application->setIcon(QUrl("youtube"));
3343+ application->setScreenshot(QString("image://application/%1/%2").arg(application->appId()).arg(QDateTime::currentMSecsSinceEpoch()));
3344 m_availableApplications.append(application);
3345 }
3346
3347@@ -484,3 +568,25 @@
3348 m_sideStage->setParentItem(shell);
3349 m_sideStage->setFlag(QQuickItem::ItemHasContents, false);
3350 }
3351+
3352+QStringList ApplicationManager::availableApplications()
3353+{
3354+ QStringList appIds;
3355+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
3356+ appIds << app->appId();
3357+ }
3358+ return appIds;
3359+}
3360+
3361+int ApplicationManager::rightMargin() const
3362+{
3363+ return m_rightMargin;
3364+}
3365+
3366+void ApplicationManager::setRightMargin(int rightMargin)
3367+{
3368+ m_rightMargin = rightMargin;
3369+ Q_FOREACH(ApplicationInfo *app, m_availableApplications) {
3370+ generateQmlStrings(app);
3371+ }
3372+}
3373
3374=== modified file 'tests/mocks/Unity/Application/ApplicationManager.h'
3375--- tests/mocks/Unity/Application/ApplicationManager.h 2013-10-11 11:37:04 +0000
3376+++ tests/mocks/Unity/Application/ApplicationManager.h 2014-04-01 18:39:46 +0000
3377@@ -45,6 +45,10 @@
3378
3379 Q_PROPERTY(bool fake READ fake CONSTANT)
3380
3381+ // Only for testing
3382+ // This can be used to place some controls to right, like make tryPhoneStage for example
3383+ Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin)
3384+
3385 public:
3386 ApplicationManager(QObject *parent = NULL);
3387 virtual ~ApplicationManager();
3388@@ -87,13 +91,22 @@
3389 Q_INVOKABLE void move(int from, int to);
3390
3391 // Application control methods
3392+ Q_INVOKABLE bool requestFocusApplication(const QString &appId) override;
3393 Q_INVOKABLE bool focusApplication(const QString &appId) override;
3394 Q_INVOKABLE void unfocusCurrentApplication() override;
3395 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, const QStringList &arguments = QStringList()) override;
3396 Q_INVOKABLE ApplicationInfo *startApplication(const QString &appId, ExecFlags flags, const QStringList &arguments = QStringList());
3397 Q_INVOKABLE bool stopApplication(const QString &appId) override;
3398+ Q_INVOKABLE bool updateScreenshot(const QString &appId) override;
3399
3400 QString focusedApplicationId() const override;
3401+ bool suspended() const;
3402+ void setSuspended(bool suspended);
3403+
3404+ // Only for testing
3405+ Q_INVOKABLE QStringList availableApplications();
3406+ int rightMargin() const;
3407+ void setRightMargin(int rightMargin);
3408
3409 Q_SIGNALS:
3410 void keyboardHeightChanged();
3411@@ -113,12 +126,15 @@
3412 void createSideStage();
3413 int m_keyboardHeight;
3414 bool m_keyboardVisible;
3415+ bool m_suspended;
3416 QList<ApplicationInfo*> m_runningApplications;
3417 QList<ApplicationInfo*> m_availableApplications;
3418 QQmlComponent *m_mainStageComponent;
3419 QQuickItem *m_mainStage;
3420 QQmlComponent *m_sideStageComponent;
3421 QQuickItem *m_sideStage;
3422+
3423+ int m_rightMargin;
3424 };
3425
3426 Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
3427
3428=== added file 'tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp'
3429--- tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp 1970-01-01 00:00:00 +0000
3430+++ tests/mocks/Unity/Application/ApplicationScreenshotProvider.cpp 2014-04-01 18:39:46 +0000
3431@@ -0,0 +1,74 @@
3432+/*
3433+ * Copyright (C) 2014 Canonical, Ltd.
3434+ *
3435+ * This program is free software: you can redistribute it and/or modify it under
3436+ * the terms of the GNU Lesser General Public License version 3, as published by
3437+ * the Free Software Foundation.
3438+ *
3439+ * This program is distributed in the hope that it will be useful, but WITHOUT
3440+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3441+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3442+ * Lesser General Public License for more details.
3443+ *
3444+ * You should have received a copy of the GNU Lesser General Public License
3445+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3446+ */
3447+
3448+#include "ApplicationScreenshotProvider.h"
3449+#include "ApplicationManager.h"
3450+#include "ApplicationInfo.h"
3451+
3452+#include "paths.h"
3453+
3454+#include <QDebug>
3455+#include <QGuiApplication>
3456+#include <QWindow>
3457+#include <QQuickWindow>
3458+
3459+ApplicationScreenshotProvider::ApplicationScreenshotProvider(ApplicationManager *appManager)
3460+ : QQuickImageProvider(QQuickImageProvider::Image)
3461+ , m_appManager(appManager)
3462+{
3463+}
3464+
3465+QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize * size,
3466+ const QSize &requestedSize)
3467+{
3468+ // We ignore requestedSize here intentionally to avoid keeping scaled copies around
3469+ Q_UNUSED(requestedSize)
3470+
3471+ QString appId = imageId.split('/').first();
3472+
3473+ ApplicationInfo* app = static_cast<ApplicationInfo*>(m_appManager->findApplication(appId));
3474+ if (app == nullptr) {
3475+ return QImage();
3476+ }
3477+
3478+ QString filePath = QString("%1/Dash/graphics/phone/screenshots/%2@12.png").arg(qmlDirectory()).arg(app->icon().toString());
3479+
3480+ QImage image;
3481+ if (!image.load(filePath)) {
3482+ qWarning() << "failed loading app image" << filePath;
3483+ }
3484+
3485+
3486+ if (app->stage() == ApplicationInfo::SideStage) {
3487+ QByteArray gus = qgetenv("GRID_UNIT_PX");
3488+ image = image.scaledToWidth(gus.toInt() * 48);
3489+ } else {
3490+ // Lets scale main stage applications to be the size of the screen/window.
3491+ QGuiApplication *unity = qobject_cast<QGuiApplication*>(qApp);
3492+ Q_FOREACH (QWindow *win, unity->allWindows()) {
3493+ QQuickWindow *quickWin = qobject_cast<QQuickWindow*>(win);
3494+ if (quickWin) {
3495+ image = image.scaledToWidth(quickWin->width() - m_appManager->rightMargin());
3496+ break;
3497+ }
3498+ }
3499+ }
3500+
3501+ size->setWidth(image.width());
3502+ size->setHeight(image.height());
3503+
3504+ return image;
3505+}
3506
3507=== added file 'tests/mocks/Unity/Application/ApplicationScreenshotProvider.h'
3508--- tests/mocks/Unity/Application/ApplicationScreenshotProvider.h 1970-01-01 00:00:00 +0000
3509+++ tests/mocks/Unity/Application/ApplicationScreenshotProvider.h 2014-04-01 18:39:46 +0000
3510@@ -0,0 +1,34 @@
3511+/*
3512+ * Copyright (C) 201 Canonical, Ltd.
3513+ *
3514+ * This program is free software: you can redistribute it and/or modify it under
3515+ * the terms of the GNU Lesser General Public License version 3, as published by
3516+ * the Free Software Foundation.
3517+ *
3518+ * This program is distributed in the hope that it will be useful, but WITHOUT
3519+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
3520+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3521+ * Lesser General Public License for more details.
3522+ *
3523+ * You should have received a copy of the GNU Lesser General Public License
3524+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3525+ */
3526+
3527+#ifndef APPLICATIONSCREENSHOTPROVIDER_H
3528+#define APPLICATIONSCREENSHOTPROVIDER_H
3529+
3530+#include <QQuickImageProvider>
3531+
3532+class ApplicationManager;
3533+class ApplicationScreenshotProvider : public QQuickImageProvider
3534+{
3535+public:
3536+ explicit ApplicationScreenshotProvider(ApplicationManager *appManager);
3537+
3538+ QImage requestImage(const QString &appId, QSize *size, const QSize &requestedSize) override;
3539+
3540+private:
3541+ ApplicationManager* m_appManager;
3542+};
3543+
3544+#endif // APPLICATIONSCREENSHOTPROVIDER_H
3545
3546=== modified file 'tests/mocks/Unity/Application/CMakeLists.txt'
3547--- tests/mocks/Unity/Application/CMakeLists.txt 2013-12-13 01:02:53 +0000
3548+++ tests/mocks/Unity/Application/CMakeLists.txt 2014-04-01 18:39:46 +0000
3549@@ -5,6 +5,7 @@
3550 ApplicationInfo.cpp
3551 ApplicationImage.cpp
3552 ApplicationManager.cpp
3553+ ApplicationScreenshotProvider.cpp
3554 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
3555 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
3556 )
3557
3558=== modified file 'tests/mocks/Unity/Application/plugin.cpp'
3559--- tests/mocks/Unity/Application/plugin.cpp 2013-09-11 15:33:02 +0000
3560+++ tests/mocks/Unity/Application/plugin.cpp 2014-04-01 18:39:46 +0000
3561@@ -18,13 +18,20 @@
3562 #include "ApplicationInfo.h"
3563 #include "ApplicationImage.h"
3564 #include "ApplicationManager.h"
3565+#include "ApplicationScreenshotProvider.h"
3566
3567 #include <qqml.h>
3568+#include <QQmlEngine>
3569+
3570+ApplicationManager *s_appManager = 0;
3571
3572 static QObject* applicationManagerSingleton(QQmlEngine* engine, QJSEngine* scriptEngine) {
3573- Q_UNUSED(engine);
3574- Q_UNUSED(scriptEngine);
3575- return new ApplicationManager();
3576+ Q_UNUSED(engine);
3577+ Q_UNUSED(scriptEngine);
3578+ if (!s_appManager) {
3579+ s_appManager = new ApplicationManager();
3580+ }
3581+ return s_appManager;
3582 }
3583
3584 void FakeUnityApplicationQmlPlugin::registerTypes(const char *uri)
3585@@ -37,3 +44,11 @@
3586
3587 qmlRegisterType<ApplicationImage>(uri, 0, 1, "ApplicationImage");
3588 }
3589+
3590+void FakeUnityApplicationQmlPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
3591+{
3592+ QQmlExtensionPlugin::initializeEngine(engine, uri);
3593+
3594+ ApplicationManager* appManager = static_cast<ApplicationManager*>(applicationManagerSingleton(engine, NULL));
3595+ engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(appManager));
3596+}
3597
3598=== modified file 'tests/mocks/Unity/Application/plugin.h'
3599--- tests/mocks/Unity/Application/plugin.h 2013-09-04 13:42:27 +0000
3600+++ tests/mocks/Unity/Application/plugin.h 2014-04-01 18:39:46 +0000
3601@@ -25,6 +25,7 @@
3602 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
3603 public:
3604 void registerTypes(const char *uri);
3605+ void initializeEngine(QQmlEngine *engine, const char *uri);
3606 };
3607
3608 #endif
3609
3610=== modified file 'tests/plugins/Unity/Launcher/launchermodeltest.cpp'
3611--- tests/plugins/Unity/Launcher/launchermodeltest.cpp 2013-09-16 17:59:13 +0000
3612+++ tests/plugins/Unity/Launcher/launchermodeltest.cpp 2014-04-01 18:39:46 +0000
3613@@ -39,6 +39,7 @@
3614 ApplicationInfoInterface::Stage stage() const { return ApplicationInfoInterface::MainStage; }
3615 ApplicationInfoInterface::State state() const { return ApplicationInfoInterface::Running; }
3616 bool focused() const { return m_focused; }
3617+ QUrl screenshot() const { return QUrl(); }
3618
3619 // Methods used for mocking (not in the interface)
3620 void setFocused(bool focused) { m_focused = focused; Q_EMIT focusedChanged(focused); }
3621@@ -92,6 +93,10 @@
3622 m_list.takeAt(index)->deleteLater();
3623 endRemoveRows();
3624 }
3625+ bool updateScreenshot(const QString &appId) { Q_UNUSED(appId); return true; }
3626+ bool requestFocusApplication(const QString &appId) { Q_UNUSED(appId); return true; }
3627+ bool suspended() const { return false; }
3628+ void setSuspended(bool) {}
3629
3630 private:
3631 QList<MockApp*> m_list;
3632
3633=== modified file 'tests/qmltests/CMakeLists.txt'
3634--- tests/qmltests/CMakeLists.txt 2014-03-27 12:37:27 +0000
3635+++ tests/qmltests/CMakeLists.txt 2014-04-01 18:39:46 +0000
3636@@ -84,6 +84,6 @@
3637 ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
3638 add_qml_test(Panel/Indicators MenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
3639 add_qml_test(Panel/Indicators MessageMenuItemFactory IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins)
3640-add_qml_test(SideStage SideStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
3641+add_qml_test(Stages PhoneStage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
3642 ${CMAKE_BINARY_DIR}/tests/mocks
3643 ${CMAKE_BINARY_DIR}/plugins)
3644
3645=== modified file 'tests/qmltests/Components/tst_ResponsiveFlowView.qml'
3646--- tests/qmltests/Components/tst_ResponsiveFlowView.qml 2013-12-17 16:04:47 +0000
3647+++ tests/qmltests/Components/tst_ResponsiveFlowView.qml 2014-04-01 18:39:46 +0000
3648@@ -82,8 +82,7 @@
3649 ResponsiveFlowView {
3650 id: flow
3651 anchors.fill: parent
3652- firstModel: fakeModel
3653- secondModel: fakeModel
3654+ model: fakeModel
3655 minimumHorizontalSpacing:
3656 minHSpacingSelector.values[minHSpacingSelector.selectedIndex]
3657 verticalSpacing: units.gu(2)
3658
3659=== modified file 'tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml'
3660--- tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2013-12-17 16:04:47 +0000
3661+++ tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml 2014-04-01 18:39:46 +0000
3662@@ -18,87 +18,20 @@
3663 import QtTest 1.0
3664 import "../../../../qml/Dash/Apps"
3665 import Unity.Test 0.1 as UT
3666+import Unity.Application 0.1
3667
3668-Item {
3669+// Using Rectangle to have an opaque surface because AppManager paints app surfaces behind it.
3670+Rectangle {
3671 width: units.gu(50)
3672 height: units.gu(40)
3673
3674- QtObject {
3675- id: fakeApplicationManager
3676-
3677- property bool sideStageEnabled: false
3678-
3679- function stopApplication(appId) {
3680- for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
3681- if (appId == fakeRunningAppsModel.get(i).appId) {
3682- fakeRunningAppsModel.remove(i)
3683- }
3684- }
3685- }
3686- }
3687-
3688- QtObject {
3689- id: shell
3690- property bool dashShown: true
3691- property bool stageScreenshotsReady: false
3692- property var applicationManager: fakeApplicationManager
3693-
3694- function activateApplication(appId) {
3695- }
3696- }
3697-
3698- ListModel {
3699- id: fakeRunningAppsModel
3700-
3701- function contains(appId) {
3702- for (var i=0, len=fakeRunningAppsModel.count; i<len; i++) {
3703- if (appId == fakeRunningAppsModel.get(i).appId) {
3704- return true;
3705- }
3706- }
3707- return false;
3708- }
3709-
3710- function rePopulate() {
3711- for (var i=0, len=availableAppsModel.count; i<len; i++) {
3712- fakeRunningAppsModel.append(availableAppsModel.get(i));
3713- }
3714- }
3715- }
3716-
3717- ListModel {
3718- id: availableAppsModel
3719- ListElement {
3720- name: "Phone"
3721- icon: "phone-app"
3722- exec: "/usr/bin/phone-app"
3723- appId: "phone"
3724- imageQml: "import QtQuick 2.0\n \
3725- Rectangle { \n \
3726- anchors.fill:parent \n \
3727- color:'darkgreen' \n \
3728- Text { anchors.centerIn: parent; text: 'PHONE' } \n \
3729- }"
3730- }
3731-
3732- ListElement {
3733- name: "Calendar"
3734- icon: "calendar-app"
3735- exec: "/usr/bin/calendar-app"
3736- appId: "calendar"
3737- imageQml: "import QtQuick 2.0\n \
3738- Rectangle { \n \
3739- anchors.fill:parent \n \
3740- color:'darkblue' \n \
3741- Text { anchors.centerIn: parent; text: 'CALENDAR'\n \
3742- color:'white'} \n \
3743- }"
3744- }
3745- }
3746-
3747 function resetRunningApplications() {
3748- fakeRunningAppsModel.clear()
3749- fakeRunningAppsModel.rePopulate()
3750+ while (ApplicationManager.count > 0) {
3751+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
3752+ }
3753+
3754+ ApplicationManager.startApplication("phone-app");
3755+ ApplicationManager.startApplication("webbrowser-app");
3756 }
3757
3758 Component.onCompleted: {
3759@@ -109,7 +42,7 @@
3760 RunningApplicationsGrid {
3761 id: runningApplicationsGrid
3762 anchors.fill: parent
3763- firstModel: fakeRunningAppsModel
3764+ model: ApplicationManager
3765 }
3766
3767 UT.UnityTestCase {
3768@@ -121,11 +54,11 @@
3769 resetRunningApplications()
3770 }
3771
3772- property var calendarTile
3773+ property var browserTile
3774 property var phoneTile
3775
3776- property var isCalendarLongPressed: false
3777- function onCalendarLongPressed() {isCalendarLongPressed = true}
3778+ property var isBrowserLongPressed: false
3779+ function onBrowserLongPressed() {isBrowserLongPressed = true}
3780
3781 property var isPhoneLongPressed: false
3782 function onPhoneLongPressed() {isPhoneLongPressed = true}
3783@@ -133,25 +66,25 @@
3784 // Tiles should go to termination mode when any one of them is long-pressed.
3785 // Long-pressing when they're in termination mode brings them back to activation mode
3786 function test_enterTerminationMode() {
3787- calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
3788- verify(calendarTile != undefined)
3789- calendarTile.onPressAndHold.connect(onCalendarLongPressed)
3790+ browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
3791+ verify(browserTile != undefined)
3792+ browserTile.onPressAndHold.connect(onBrowserLongPressed)
3793
3794 phoneTile = findChild(runningApplicationsGrid, "runningAppTile Phone")
3795 verify(phoneTile != undefined)
3796 phoneTile.onPressAndHold.connect(onPhoneLongPressed)
3797
3798- compare(calendarTile.terminationModeEnabled, false)
3799+ compare(browserTile.terminationModeEnabled, false)
3800 compare(phoneTile.terminationModeEnabled, false)
3801 compare(runningApplicationsGrid.terminationModeEnabled, false)
3802
3803- isCalendarLongPressed = false
3804- mousePress(calendarTile, calendarTile.width/2, calendarTile.height/2)
3805+ isBrowserLongPressed = false
3806+ mousePress(browserTile, browserTile.width/2, browserTile.height/2)
3807 tryCompareFunction(checkSwitchToTerminationModeAfterLongPress, true)
3808
3809- mouseRelease(calendarTile, calendarTile.width/2, calendarTile.height/2)
3810+ mouseRelease(browserTile, browserTile.width/2, browserTile.height/2)
3811
3812- compare(calendarTile.terminationModeEnabled, true)
3813+ compare(browserTile.terminationModeEnabled, true)
3814 compare(phoneTile.terminationModeEnabled, true)
3815 compare(runningApplicationsGrid.terminationModeEnabled, true)
3816
3817@@ -161,23 +94,23 @@
3818
3819 mouseRelease(phoneTile, phoneTile.width/2, phoneTile.height/2)
3820
3821- compare(calendarTile.terminationModeEnabled, false)
3822+ compare(browserTile.terminationModeEnabled, false)
3823 compare(phoneTile.terminationModeEnabled, false)
3824 compare(runningApplicationsGrid.terminationModeEnabled, false)
3825
3826- calendarTile.onPressAndHold.disconnect(onCalendarLongPressed)
3827+ browserTile.onPressAndHold.disconnect(onBrowserLongPressed)
3828 phoneTile.onPressAndHold.disconnect(onPhoneLongPressed)
3829 }
3830
3831 // Checks that components swicth to termination mode after (and only after) a long
3832- // press happens on Calendar tile.
3833+ // press happens on Browser tile.
3834 function checkSwitchToTerminationModeAfterLongPress() {
3835- compare(calendarTile.terminationModeEnabled, isCalendarLongPressed)
3836- compare(phoneTile.terminationModeEnabled, isCalendarLongPressed)
3837- compare(runningApplicationsGrid.terminationModeEnabled, isCalendarLongPressed)
3838+ compare(browserTile.terminationModeEnabled, isBrowserLongPressed)
3839+ compare(phoneTile.terminationModeEnabled, isBrowserLongPressed)
3840+ compare(runningApplicationsGrid.terminationModeEnabled, isBrowserLongPressed)
3841
3842- return isCalendarLongPressed &&
3843- calendarTile.terminationModeEnabled &&
3844+ return isBrowserLongPressed &&
3845+ browserTile.terminationModeEnabled &&
3846 phoneTile.terminationModeEnabled &&
3847 runningApplicationsGrid.terminationModeEnabled
3848 }
3849@@ -185,12 +118,12 @@
3850 // Checks that components swicth to activation mode after (and only after) a long
3851 // press happens on Phone tile.
3852 function checkSwitchToActivationModeAfterLongPress() {
3853- compare(calendarTile.terminationModeEnabled, !isPhoneLongPressed)
3854+ compare(browserTile.terminationModeEnabled, !isPhoneLongPressed)
3855 compare(phoneTile.terminationModeEnabled, !isPhoneLongPressed)
3856 compare(runningApplicationsGrid.terminationModeEnabled, !isPhoneLongPressed)
3857
3858 return isPhoneLongPressed &&
3859- !calendarTile.terminationModeEnabled &&
3860+ !browserTile.terminationModeEnabled &&
3861 !phoneTile.terminationModeEnabled &&
3862 !runningApplicationsGrid.terminationModeEnabled
3863 }
3864@@ -200,17 +133,17 @@
3865 function test_clickTileNotClose() {
3866 runningApplicationsGrid.terminationModeEnabled = true
3867
3868- var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
3869- verify(calendarTile != undefined)
3870-
3871- verify(fakeRunningAppsModel.contains("calendar"))
3872-
3873- mouseClick(calendarTile, calendarTile.width/2, calendarTile.height/2)
3874-
3875- verify(fakeRunningAppsModel.contains("calendar"))
3876-
3877- // The tile for the Calendar app should stay there
3878- tryCompareFunction(checkCalendarTileExists, true)
3879+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
3880+ verify(browserTile != undefined)
3881+
3882+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
3883+
3884+ mouseClick(browserTile, browserTile.width/2, browserTile.height/2)
3885+
3886+ verify(ApplicationManager.findApplication("webbrowser-app") !== null)
3887+
3888+ // The tile for the Browser app should stay there
3889+ tryCompareFunction(checkBrowserTileExists, true)
3890 }
3891
3892 // While in termination mode, clicking on a running application tile's close icon
3893@@ -218,25 +151,25 @@
3894 function test_clickCloseIconToTerminateApp() {
3895 runningApplicationsGrid.terminationModeEnabled = true
3896
3897- var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
3898- var calendarTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Calendar")
3899-
3900- verify(calendarTile != undefined)
3901- verify(calendarTileCloseButton != undefined)
3902- verify(fakeRunningAppsModel.contains("calendar"))
3903-
3904- mouseClick(calendarTileCloseButton, calendarTileCloseButton.width/2, calendarTileCloseButton.height/2)
3905+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
3906+ var browserTileCloseButton = findChild(runningApplicationsGrid, "closeIcon Browser")
3907+
3908+ verify(browserTile != undefined)
3909+ verify(browserTileCloseButton != undefined)
3910+ verify(ApplicationManager.findApplication("webbrowser-app") !== 0)
3911+
3912+ mouseClick(browserTileCloseButton, browserTileCloseButton.width/2, browserTileCloseButton.height/2)
3913 wait(0) // spin event loop to start any pending animation
3914
3915- verify(!fakeRunningAppsModel.contains("calendar"))
3916+ verify(ApplicationManager.findApplication("webbrowser-app") === null)
3917
3918- // The tile for the Calendar app should eventually vanish since the
3919+ // The tile for the Browser app should eventually vanish since the
3920 // application has been terminated.
3921- tryCompareFunction(checkCalendarTileExists, false)
3922+ tryCompareFunction(checkBrowserTileExists, false)
3923 }
3924
3925- function checkCalendarTileExists() {
3926- return findChild(runningApplicationsGrid, "runningAppTile Calendar")
3927+ function checkBrowserTileExists() {
3928+ return findChild(runningApplicationsGrid, "runningAppTile Browser")
3929 != undefined
3930 }
3931
3932@@ -245,8 +178,8 @@
3933 function test_clickOutsideTilesDisablesTerminationMode() {
3934 runningApplicationsGrid.terminationModeEnabled = true
3935
3936- var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
3937- verify(calendarTile != undefined)
3938+ var browserTile = findChild(runningApplicationsGrid, "runningAppTile Browser")
3939+ verify(browserTile != undefined)
3940
3941 verify(runningApplicationsGrid.terminationModeEnabled);
3942
3943
3944=== added directory 'tests/qmltests/Stages'
3945=== added file 'tests/qmltests/Stages/tst_PhoneStage.qml'
3946--- tests/qmltests/Stages/tst_PhoneStage.qml 1970-01-01 00:00:00 +0000
3947+++ tests/qmltests/Stages/tst_PhoneStage.qml 2014-04-01 18:39:46 +0000
3948@@ -0,0 +1,288 @@
3949+/*
3950+ * Copyright 2014 Canonical Ltd.
3951+ *
3952+ * This program is free software; you can redistribute it and/or modify
3953+ * it under the terms of the GNU General Public License as published by
3954+ * the Free Software Foundation; version 3.
3955+ *
3956+ * This program is distributed in the hope that it will be useful,
3957+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3958+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3959+ * GNU General Public License for more details.
3960+ *
3961+ * You should have received a copy of the GNU General Public License
3962+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3963+ */
3964+
3965+import QtQuick 2.0
3966+import QtTest 1.0
3967+import Unity.Test 0.1 as UT
3968+import ".."
3969+import "../../../qml/Stages"
3970+import Ubuntu.Components 0.1
3971+import Unity.Application 0.1
3972+
3973+Item {
3974+ width: units.gu(70)
3975+ height: units.gu(70)
3976+
3977+ Rectangle {
3978+
3979+ }
3980+
3981+ PhoneStage {
3982+ id: phoneStage
3983+ anchors { fill: parent; rightMargin: units.gu(30) }
3984+ shown: true
3985+ dragAreaWidth: units.gu(2)
3986+ }
3987+
3988+ Binding {
3989+ target: ApplicationManager
3990+ property: "rightMargin"
3991+ value: phoneStage.anchors.rightMargin
3992+ }
3993+
3994+ Rectangle {
3995+ anchors { fill: parent; leftMargin: phoneStage.width }
3996+// color: "blue"
3997+
3998+ Column {
3999+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
4000+ spacing: units.gu(1)
4001+ Button {
4002+ anchors { left: parent.left; right: parent.right }
4003+ text: "Add App"
4004+ onClicked: {
4005+ testCase.addApps();
4006+ }
4007+ }
4008+ Button {
4009+ anchors { left: parent.left; right: parent.right }
4010+ text: "Add App"
4011+ }
4012+ }
4013+ }
4014+
4015+ UT.UnityTestCase {
4016+ id: testCase
4017+ name: "PhoneStage"
4018+ when: windowShown
4019+
4020+ function addApps(count) {
4021+ if (count == undefined) count = 1;
4022+ for (var i = 0; i < count; i++) {
4023+ var app = ApplicationManager.startApplication(ApplicationManager.availableApplications()[ApplicationManager.count])
4024+ tryCompare(app, "state", ApplicationInfoInterface.Running)
4025+ // Fixme: Right now there is a timeout in the PhoneStage that displays a white splash
4026+ // screen rectangle when an app starts. This is because we don't yet have a way of
4027+ // knowing when an app has finished launching. That workaround and this wait() should
4028+ // go away at some point and the app's state only changing to Running when ready for real.
4029+// wait(1000)
4030+ waitForRendering(phoneStage)
4031+ }
4032+ }
4033+
4034+ function goToSpread() {
4035+ var spreadView = findChild(phoneStage, "spreadView");
4036+
4037+ var startX = phoneStage.width;
4038+ var startY = phoneStage.height / 2;
4039+ var endY = startY;
4040+ var endX = units.gu(2);
4041+
4042+ touchFlick(phoneStage, startX, startY, endX, endY,
4043+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
4044+ }
4045+
4046+ function test_shortFlick() {
4047+ addApps(2)
4048+ var startX = phoneStage.width - units.gu(1);
4049+ var startY = phoneStage.height / 2;
4050+ var endX = phoneStage.width / 2;
4051+ var endY = startY;
4052+
4053+ var activeApp = ApplicationManager.get(0);
4054+ var inactiveApp = ApplicationManager.get(1);
4055+
4056+ touchFlick(phoneStage, startX, startY, endX, endY,
4057+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
4058+
4059+ tryCompare(ApplicationManager, "focusedApplicationId", inactiveApp.appId)
4060+
4061+ touchFlick(phoneStage, startX, startY, endX, endY,
4062+ true /* beginTouch */, true /* endTouch */, units.gu(10), 50);
4063+
4064+ tryCompare(ApplicationManager, "focusedApplicationId", activeApp.appId)
4065+
4066+ tryCompare(phoneStage, "painting", false);
4067+ }
4068+
4069+ function test_enterSpread_data() {
4070+ return [
4071+ {tag: "<position1 (linear movement)", positionMarker: "positionMarker1", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
4072+ {tag: "<position1 (non-linear movement)", positionMarker: "positionMarker1", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 0 },
4073+ {tag: ">position1", positionMarker: "positionMarker1", linear: true, offset: +1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
4074+ {tag: "<position2 (linear)", positionMarker: "positionMarker2", linear: true, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
4075+ {tag: "<position2 (non-linear)", positionMarker: "positionMarker2", linear: false, offset: -1, endPhase: 0, targetPhase: 0, newFocusedIndex: 1 },
4076+ {tag: ">position2", positionMarker: "positionMarker2", linear: true, offset: +1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
4077+ {tag: "<position3", positionMarker: "positionMarker3", linear: true, offset: -1, endPhase: 1, targetPhase: 0, newFocusedIndex: 1 },
4078+ {tag: ">position3", positionMarker: "positionMarker3", linear: true, offset: +1, endPhase: 2, targetPhase: 2, newFocusedIndex: 2 },
4079+ ];
4080+ }
4081+
4082+ function test_enterSpread(data) {
4083+ addApps(5)
4084+
4085+ var spreadView = findChild(phoneStage, "spreadView");
4086+
4087+ var startX = phoneStage.width;
4088+ var startY = phoneStage.height / 2;
4089+ var endY = startY;
4090+ var endX = spreadView.width - (spreadView.width * spreadView[data.positionMarker]) - data.offset - phoneStage.dragAreaWidth;
4091+
4092+ var oldFocusedApp = ApplicationManager.get(0);
4093+ var newFocusedApp = ApplicationManager.get(data.newFocusedIndex);
4094+
4095+ touchFlick(phoneStage, startX, startY, endX, endY,
4096+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
4097+
4098+ tryCompare(spreadView, "phase", data.endPhase)
4099+
4100+ if (!data.linear) {
4101+ touchFlick(phoneStage, endX, endY, endX + units.gu(.5), endY,
4102+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
4103+ touchFlick(phoneStage, endY + units.gu(.5), endY, endX, endY,
4104+ false /* beginTouch */, false /* endTouch */, units.gu(10), 50);
4105+ }
4106+
4107+ touchRelease(phoneStage, endX, endY);
4108+
4109+ tryCompare(spreadView, "phase", data.targetPhase)
4110+
4111+ if (data.targetPhase == 2) {
4112+ var app2 = findChild(spreadView, "appDelegate2");
4113+ mouseClick(app2, units.gu(1), units.gu(1));
4114+ }
4115+
4116+ tryCompare(phoneStage, "painting", false);
4117+ tryCompare(ApplicationManager, "focusedApplicationId", newFocusedApp.appId);
4118+ }
4119+
4120+ function test_selectAppFromSpread_data() {
4121+ var appsToTest = 6;
4122+ var apps = new Array();
4123+ for (var i = 0; i < appsToTest; i++) {
4124+ var item = new Object();
4125+ item.tag = "App " + i;
4126+ item.index = i;
4127+ item.total = appsToTest;
4128+ apps.push(item)
4129+ }
4130+ return apps;
4131+ }
4132+
4133+ function test_selectAppFromSpread(data) {
4134+ addApps(data.total)
4135+
4136+ var spreadView = findChild(phoneStage, "spreadView");
4137+
4138+ goToSpread();
4139+
4140+ tryCompare(spreadView, "phase", 2);
4141+
4142+ var tile = findChild(spreadView, "appDelegate" + data.index);
4143+ var appId = ApplicationManager.get(data.index).appId;
4144+
4145+ if (tile.mapToItem(spreadView).x > spreadView.width) {
4146+ // Item is not visible... Need to flick the spread
4147+ var startX = phoneStage.width - units.gu(1);
4148+ var startY = phoneStage.height / 2;
4149+ var endY = startY;
4150+ var endX = units.gu(2);
4151+ touchFlick(phoneStage, startX, startY, endX, endY, true, true, units.gu(10), 50)
4152+ tryCompare(spreadView, "flicking", false);
4153+ tryCompare(spreadView, "moving", false);
4154+// waitForRendering(phoneStage);
4155+ }
4156+
4157+ console.log("clicking app", data.index, "(", appId, ")")
4158+ mouseClick(spreadView, tile.mapToItem(spreadView).x + units.gu(1), spreadView.height / 2)
4159+ tryCompare(ApplicationManager, "focusedApplicationId", appId);
4160+ tryCompare(spreadView, "phase", 0);
4161+ }
4162+
4163+ function test_animateAppStartup() {
4164+ compare(phoneStage.painting, false);
4165+ addApps(2);
4166+ tryCompare(phoneStage, "painting", true);
4167+ tryCompare(phoneStage, "painting", false);
4168+ addApps(1);
4169+ tryCompare(phoneStage, "painting", true);
4170+ tryCompare(phoneStage, "painting", false);
4171+ }
4172+
4173+ function test_select_data() {
4174+ return [
4175+ { tag: "0", index: 0 },
4176+ { tag: "2", index: 2 },
4177+ { tag: "4", index: 4 },
4178+ ]
4179+ }
4180+
4181+ function test_select(data) {
4182+ addApps(5);
4183+
4184+ var spreadView = findChild(phoneStage, "spreadView");
4185+ var selectedApp = ApplicationManager.get(data.index);
4186+
4187+ goToSpread();
4188+
4189+ phoneStage.select(selectedApp.appId);
4190+
4191+ tryCompare(phoneStage, "painting", false);
4192+ compare(ApplicationManager.focusedApplicationId, selectedApp.appId);
4193+ }
4194+
4195+ function test_fullscreenMode() {
4196+ var fullscreenApp = null;
4197+ var normalApp = null;
4198+
4199+ for (var i = 0; i < 5; i++) {
4200+ addApps(1);
4201+ var newApp = ApplicationManager.get(0);
4202+ tryCompare(phoneStage, "fullscreen", newApp.fullscreen);
4203+ if (newApp.fullscreen && fullscreenApp == null) {
4204+ fullscreenApp = newApp;
4205+ } else if (!newApp.fullscreen && normalApp == null){
4206+ normalApp = newApp;
4207+ }
4208+ }
4209+ verify(fullscreenApp != null); // Can't continue the test without having a fullscreen app
4210+ verify(normalApp != null); // Can't continue the test without having a non-fullscreen app
4211+
4212+ // Select a normal app
4213+ goToSpread();
4214+ phoneStage.select(normalApp.appId);
4215+ tryCompare(phoneStage, "fullscreen", false);
4216+
4217+ // Select a fullscreen app
4218+ goToSpread();
4219+ phoneStage.select(fullscreenApp.appId);
4220+ tryCompare(phoneStage, "fullscreen", true);
4221+
4222+ // Select a normal app
4223+ goToSpread();
4224+ phoneStage.select(normalApp.appId);
4225+ tryCompare(phoneStage, "fullscreen", false);
4226+ }
4227+
4228+ function cleanup() {
4229+ while (ApplicationManager.count > 0) {
4230+ var oldCount = ApplicationManager.count;
4231+ ApplicationManager.stopApplication(ApplicationManager.get(0).appId)
4232+ tryCompare(ApplicationManager, "count", oldCount - 1)
4233+ }
4234+ }
4235+ }
4236+}
4237
4238=== modified file 'tests/qmltests/tst_Shell.qml'
4239--- tests/qmltests/tst_Shell.qml 2014-03-19 10:48:06 +0000
4240+++ tests/qmltests/tst_Shell.qml 2014-04-01 18:39:46 +0000
4241@@ -117,6 +117,7 @@
4242 while (apps.count > 0) {
4243 ApplicationManager.stopApplication(apps.get(0).appId);
4244 }
4245+ compare(ApplicationManager.count, 0)
4246 }
4247
4248 /*
4249@@ -187,26 +188,34 @@
4250 tapOnAppIconInLauncher();
4251 waitUntilApplicationWindowIsFullyVisible();
4252
4253- var mainApp = ApplicationManager.focusedApplicationId;
4254- verify(mainApp != "");
4255+ var mainAppId = ApplicationManager.focusedApplicationId;
4256+ verify(mainAppId != "");
4257+ var mainApp = ApplicationManager.findApplication(mainAppId);
4258+ verify(mainApp);
4259+ tryCompare(mainApp.state, ApplicationInfo.Running);
4260
4261 // Try to suspend while proximity is engaged...
4262 Powerd.displayPowerStateChange(Powerd.Off, Powerd.UseProximity);
4263 tryCompare(greeter, "showProgress", 0);
4264
4265 // Now really suspend
4266+ print("suspending")
4267 Powerd.displayPowerStateChange(Powerd.Off, 0);
4268+ print("done suspending")
4269 tryCompare(greeter, "showProgress", 1);
4270- tryCompare(ApplicationManager, "focusedApplicationId", "");
4271+
4272+ tryCompare(ApplicationManager, "suspended", true);
4273+ compare(mainApp.state, ApplicationInfo.Suspended);
4274
4275 // And wake up
4276 Powerd.displayPowerStateChange(Powerd.On, 0);
4277- tryCompare(ApplicationManager, "focusedApplicationId", "");
4278 tryCompare(greeter, "showProgress", 1);
4279
4280 // Swipe away greeter to focus app
4281 swipeAwayGreeter();
4282- tryCompare(ApplicationManager, "focusedApplicationId", mainApp);
4283+ tryCompare(ApplicationManager, "suspended", false);
4284+ compare(mainApp.state, ApplicationInfo.Running);
4285+ tryCompare(ApplicationManager, "focusedApplicationId", mainAppId);
4286 }
4287
4288 function swipeAwayGreeter() {
4289@@ -239,7 +248,7 @@
4290 tryCompare(dash, "opacity", 1.0);
4291
4292 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
4293- true /* beginTouch */, false /* endTouch */);
4294+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
4295
4296 // check that Dash has been scaled down and had its opacity reduced
4297 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
4298@@ -270,7 +279,7 @@
4299 tryCompare(dash, "opacity", 1.0);
4300
4301 touchFlick(shell, touchX, touchY, shell.width * 0.1, touchY,
4302- true /* beginTouch */, false /* endTouch */);
4303+ true /* beginTouch */, false /* endTouch */, units.gu(10), 50);
4304
4305 // check that Dash has been scaled down and had its opacity reduced
4306 tryCompareFunction(function() { return dash.contentScale <= 0.9; }, true);
4307@@ -486,7 +495,6 @@
4308 }
4309
4310 function test_DashShown(data) {
4311-
4312 if (data.greeter) {
4313 // Swipe the greeter in
4314 var greeter = findChild(shell, "greeter");

Subscribers

People subscribed via source and target branches