Merge lp:~mterry/unity8/tutorial-new-screens into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Michael Zanetti
Approved revision: 1410
Merged at revision: 1604
Proposed branch: lp:~mterry/unity8/tutorial-new-screens
Merge into: lp:unity8
Prerequisite: lp:~mterry/unity8/tutorial-refactor
Diff against target: 1549 lines (+769/-226)
16 files modified
qml/Shell.qml (+7/-3)
qml/Stages/ApplicationWindow.qml (+14/-1)
qml/Stages/PhoneStage.qml (+62/-34)
qml/Stages/TransformedSpreadDelegate.qml (+27/-21)
qml/Tutorial/Slider.qml (+8/-2)
qml/Tutorial/Tick.qml (+29/-0)
qml/Tutorial/Tutorial.qml (+8/-1)
qml/Tutorial/TutorialBottom.qml (+104/-0)
qml/Tutorial/TutorialBottomFinish.qml (+41/-0)
qml/Tutorial/TutorialContent.qml (+69/-75)
qml/Tutorial/TutorialLeftFinish.qml (+4/-10)
qml/Tutorial/TutorialPage.qml (+79/-74)
qml/Tutorial/TutorialRight.qml (+223/-0)
tests/autopilot/unity8/shell/emulators/tutorial.py (+24/-4)
tests/autopilot/unity8/shell/tests/test_tutorial.py (+7/-0)
tests/qmltests/Tutorial/tst_Tutorial.qml (+63/-1)
To merge this branch: bzr merge lp:~mterry/unity8/tutorial-new-screens
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michael Zanetti (community) Approve
Review via email: mp+245699@code.launchpad.net

Commit message

Add new right-edge and bottom-edge screens to the first-boot edge tutorial

Description of the change

Add new right-edge and bottom-edge screens to the first-boot edge tutorial.

I redesigned TutorialContent a bit to not use a loader, but pre-load everything. This prevented a delay between the Left and Right edge screens as it loads the fake app images. We already have a loader in Tutorial.qml, so I felt the second layer was unnecessary.

I also have added bits here and there to PhoneStage in order to support the right-edge tutorial screen.

Design spec here: https://docs.google.com/a/canonical.com/presentation/d/1M8S1wTo-5lIFaIxxgls9TCEnvR79XThGEVpRtkHJY_A/edit#slide=id.g451b9c9a8_062

To test, install the debs from this MP and run the following: phablet-config edges-intro --enable; adb reboot

== Checklist ==

 * Are there any related MPs required for this MP to build/function as expected? Please list.
 Yes, the pre-requisite linked to this MP

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

 * Did you make sure that your branch does not contain spurious tags?
 Yes

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

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

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1394. By Michael Terry

Merge from refactor branch

1395. By Michael Terry

Robustify waiting for demoEdgesChanged signal by not doing it

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: Approve (continuous-integration)
1396. By Michael Terry

Merge from -refactor branch

1397. By Michael Terry

Merge from -refactor

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
Michael Zanetti (mzanetti) wrote :

I tested the spread, still seems to work fine. I'm a bit worried that doing changes in the spread in the future might break the welcome wizard as its not totally obvious why your changes would be required if one only looks at the real right edge spread. I guess it would be beneficial to add some tests that verify your changes keep working. What do you think?

review: Needs Information
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 :

There seem to be somewhat legitimate (if flaky) qmltest failures, please investigate.

Revision history for this message
Albert Astals Cid (aacid) wrote :

qmltestrunner.PhoneStage::test_enterSpread is failing randomly in some other places but testTutorial.xml does indeed seem to be relative to this one

1398. By Michael Terry

Merge from -refactor

1399. By Michael Terry

Add a couple tests

Revision history for this message
Michael Terry (mterry) wrote :

@mzanetti, I've added a test that pokes and prods some spread values to make sure that they are what we expect (like the first app delegate having x==shell.width instead of 0 for example).

It's hard to test the new spread mode fully because it modifies how the transforms are applied as mostly a visual thing. What I really want to be able to write is a "does it look right?" test, with maybe some Mike AI that knows the correct answer. But ah well. Checking some values will have to do for now.

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)
1400. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1401. By Michael Terry

Merge from -refactor

1402. By Michael Terry

Updated fake screenshots

1403. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1404. By Michael Terry

Update facebook.png from design

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1405. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

Text conflict in qml/Shell.qml
1 conflicts encountered.

1406. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1407. By Michael Terry

Make spread pages slightly smaller

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1408. By Michael Terry

Merge from -refactor

1409. By Michael Terry

Add tease to bottom edge slider

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

there's an outdated comment now, see inline comment

Revision history for this message
Michael Terry (mterry) wrote :

Naw, I think that comment is still fine. I replied inline. But I'm happy to change if you disagree.

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

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

yes

 * Did CI run pass? If not, please explain why.

no. ci seems to have crashed. I've just triggered a rebuild. Michael, please check if jenkins is fine with this branch before we land it.

 * Did you make sure that the branch does not contain spurious tags?

yes

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

ok, Jenkins built fine now. Failing qml tests don't seem to be related to this branch. However, Autopilot seems to have issues. I think at least this test is broken by this branch:

unity8.shell.tests.test_edges_demo.EdgesDemoTestCase.test_complete_edge_demo

Please also check if the other AP failures are related.

review: Needs Fixing
1410. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Terry (mterry) wrote :

This is from Olga reviewing the latest code here:

"We reviewed the new implementation and you have the go ahead. :)
Looks brilliant! :D"

So that's a +1 from design. Just need to confirm fixed AP tests and we're good.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

ok. last test run looks good.

review: Approve
1411. By Michael Terry

Merge from -refactor

1412. By Michael Terry

Merge from -refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1413. By Michael Terry

Merge from tutorial-refactor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Shell.qml'
2--- qml/Shell.qml 2015-02-11 16:32:24 +0000
3+++ qml/Shell.qml 2015-02-11 16:32:25 +0000
4@@ -290,7 +290,7 @@
5 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
6 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
7
8- property bool interactive: tutorial.stagesEnabled
9+ property bool interactive: tutorial.spreadEnabled
10 && !greeter.shown
11 && !lockscreen.shown
12 && panel.indicators.fullyClosed
13@@ -323,7 +323,7 @@
14 Binding {
15 target: applicationsDisplayLoader.item
16 property: "spreadEnabled"
17- value: tutorial.stagesEnabled && !greeter.hasLockedApp
18+ value: tutorial.spreadEnabled && !greeter.hasLockedApp
19 }
20 Binding {
21 target: applicationsDisplayLoader.item
22@@ -889,8 +889,12 @@
23 panel: panel
24 stages: stages
25 overlay: overlay
26+ edgeSize: shell.edgeSize
27
28- onFinished: AccountsService.demoEdges = false
29+ onFinished: {
30+ AccountsService.demoEdges = false;
31+ active = false; // for immediate response / if AS is having problems
32+ }
33 }
34
35 Connections {
36
37=== modified file 'qml/Stages/ApplicationWindow.qml'
38--- qml/Stages/ApplicationWindow.qml 2015-02-11 16:32:24 +0000
39+++ qml/Stages/ApplicationWindow.qml 2015-02-11 16:32:25 +0000
40@@ -43,6 +43,7 @@
41 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
42 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
43 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
44+ readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : ""
45
46 // Whether the Application had a surface before but lost it.
47 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
48@@ -72,7 +73,7 @@
49 Image {
50 id: screenshotImage
51 objectName: "screenshotImage"
52- source: ""
53+ source: d.defaultScreenshot
54 anchors.fill: parent
55 antialiasing: !root.interactive
56
57@@ -125,6 +126,7 @@
58 }
59
60 StateGroup {
61+ id: stateGroup
62 objectName: "applicationWindowStateGroup"
63 states: [
64 State {
65@@ -218,6 +220,17 @@
66 }
67 },
68 Transition {
69+ from: "splashScreen"; to: "screenshot"
70+ SequentialAnimation {
71+ PropertyAction { target: screenshotImage
72+ property: "visible"; value: true }
73+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
74+ from: 0.0; to: 1.0
75+ duration: UbuntuAnimation.BriskDuration }
76+ PropertyAction { target: splashLoader; property: "active"; value: false }
77+ }
78+ },
79+ Transition {
80 from: "surface"; to: "void"
81 SequentialAnimation {
82 PropertyAction { target: sessionContainer.surfaceContainer; property: "visible"; value: false }
83
84=== modified file 'qml/Stages/PhoneStage.qml'
85--- qml/Stages/PhoneStage.qml 2015-02-11 16:32:24 +0000
86+++ qml/Stages/PhoneStage.qml 2015-02-11 16:32:25 +0000
87@@ -32,6 +32,21 @@
88 property bool spreadEnabled: true // If false, animations and right edge will be disabled
89 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
90 property int orientation: Qt.PortraitOrientation
91+ property QtObject applicationManager: ApplicationManager
92+ property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
93+ property bool altTabEnabled: true
94+ property real startScale: 1.1
95+ property real endScale: 0.7
96+
97+ // How far left the stage has been dragged
98+ readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
99+
100+ readonly property alias dragging: spreadDragArea.dragging
101+
102+ // Only used by the tutorial right now, when it is teasing the right edge
103+ property real dragAreaOverlap
104+
105+ signal opened()
106
107 color: "#111111"
108
109@@ -52,30 +67,30 @@
110 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
111 // left edge drag released. Minimum distance is given by design.
112 if (priv.oldInverseProgress > units.gu(22)) {
113- ApplicationManager.requestFocusApplication("unity8-dash");
114+ applicationManager.requestFocusApplication("unity8-dash");
115 }
116 }
117 priv.oldInverseProgress = inverseProgress;
118 }
119
120 Connections {
121- target: ApplicationManager
122+ target: applicationManager
123
124 onFocusRequested: {
125 if (spreadView.phase > 0) {
126 spreadView.snapTo(priv.indexOf(appId));
127 } else {
128- ApplicationManager.focusApplication(appId);
129+ applicationManager.focusApplication(appId);
130 }
131 }
132
133 onApplicationAdded: {
134 if (spreadView.phase == 2) {
135- spreadView.snapTo(ApplicationManager.count - 1);
136+ spreadView.snapTo(applicationManager.count - 1);
137 } else {
138 spreadView.phase = 0;
139 spreadView.contentX = -spreadView.shift;
140- ApplicationManager.focusApplication(appId);
141+ applicationManager.focusApplication(appId);
142 }
143 }
144
145@@ -90,9 +105,9 @@
146 }
147
148 function focusTopMostApp() {
149- if (ApplicationManager.count > 0) {
150- var topmostApp = ApplicationManager.get(0);
151- ApplicationManager.focusApplication(topmostApp.appId);
152+ if (applicationManager.count > 0) {
153+ var topmostApp = applicationManager.get(0);
154+ applicationManager.focusApplication(topmostApp.appId);
155 }
156 }
157 }
158@@ -100,20 +115,21 @@
159 QtObject {
160 id: priv
161
162- property string focusedAppId: ApplicationManager.focusedApplicationId
163- property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
164+ readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
165+ property string focusedAppId: applicationManager.focusedApplicationId
166+ property var focusedApplication: applicationManager.findApplication(focusedAppId)
167 property var focusedAppDelegate: null
168
169 property real oldInverseProgress: 0
170- property bool animateX: true
171+ property bool animateX: false
172
173 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
174
175 onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
176
177 function indexOf(appId) {
178- for (var i = 0; i < ApplicationManager.count; i++) {
179- if (ApplicationManager.get(i).appId == appId) {
180+ for (var i = 0; i < applicationManager.count; i++) {
181+ if (applicationManager.get(i).appId == appId) {
182 return i;
183 }
184 }
185@@ -133,7 +149,7 @@
186
187 // This indicates when the spreadView is active. That means, all the animations
188 // are activated and tiles need to line up for the spread.
189- readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized
190+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
191
192 // The flickable needs to fill the screen in order to get touch events all over.
193 // However, we don't want to the user to be able to scroll back all the way. For
194@@ -199,10 +215,17 @@
195 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
196 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
197 snapAnimation.start();
198+ root.opened();
199 }
200 }
201 function snapTo(index) {
202- if (ApplicationManager.count <= index) {
203+ if (!root.altTabEnabled) {
204+ // Reset to start instead
205+ snapAnimation.targetContentX = -shift;
206+ snapAnimation.start();
207+ return;
208+ }
209+ if (applicationManager.count <= index) {
210 // In case we're trying to snap to some non existing app, lets snap back to the first one
211 index = 0;
212 }
213@@ -217,10 +240,12 @@
214 snapAnimation.start();
215 }
216
217- // In case the ApplicationManager already holds an app when starting up we're missing animations
218+ // In case the applicationManager already holds an app when starting up we're missing animations
219 // Make sure we end up in the same state
220 Component.onCompleted: {
221 spreadView.contentX = -spreadView.shift
222+ priv.animateX = true;
223+ snapAnimation.complete();
224 }
225
226 SequentialAnimation {
227@@ -237,7 +262,7 @@
228 ScriptAction {
229 script: {
230 if (spreadView.selectedIndex >= 0) {
231- ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
232+ applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId);
233
234 spreadView.selectedIndex = -1;
235 spreadView.phase = 0;
236@@ -252,7 +277,7 @@
237 // This width controls how much the spread can be flicked left/right. It's composed of:
238 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
239 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
240- width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
241+ width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
242 height: parent.height
243 Behavior on width {
244 enabled: spreadView.closingIndex >= 0
245@@ -267,20 +292,22 @@
246 x: spreadView.contentX
247
248 onClicked: {
249- spreadView.snapTo(0);
250+ if (root.altTabEnabled) {
251+ spreadView.snapTo(0);
252+ }
253 }
254
255 Repeater {
256 id: spreadRepeater
257 objectName: "spreadRepeater"
258- model: ApplicationManager
259+ model: applicationManager
260 delegate: TransformedSpreadDelegate {
261 id: appDelegate
262 objectName: "appDelegate" + index
263 startAngle: 45
264 endAngle: 5
265- startScale: 1.1
266- endScale: 0.7
267+ startScale: root.startScale
268+ endScale: root.endScale
269 startDistance: spreadView.tileDistance
270 endDistance: units.gu(.5)
271 width: spreadView.width
272@@ -288,11 +315,12 @@
273 selected: spreadView.selectedIndex == index
274 otherSelected: spreadView.selectedIndex >= 0 && !selected
275 interactive: !spreadView.interactive && spreadView.phase === 0
276- && spreadView.shiftedContentX === 0 && root.interactive && index === 0
277- swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
278+ && spreadView.shiftedContentX === 0 && root.interactive && isFocused
279+ swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
280 maximizedAppTopMargin: root.maximizedAppTopMargin
281 dropShadow: spreadView.active ||
282 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
283+ focusFirstApp: root.focusFirstApp
284
285 readonly property bool isDash: model.appId == "unity8-dash"
286
287@@ -300,7 +328,7 @@
288
289 x: {
290 // focused app is always positioned at 0 except when following left edge drag
291- if (index == 0) {
292+ if (isFocused) {
293 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
294 return root.inverseProgress;
295 }
296@@ -311,10 +339,10 @@
297 }
298
299 // Otherwise line up for the spread
300- return spreadView.width + (index - 1) * spreadView.tileDistance;
301+ return spreadView.width + spreadIndex * spreadView.tileDistance;
302 }
303
304- application: ApplicationManager.get(index)
305+ application: applicationManager.get(index)
306 closeable: !isDash
307
308 property real behavioredIndex: index
309@@ -352,7 +380,7 @@
310 progress: {
311 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
312 // Tile 1 needs to move directly from the beginning...
313- if (behavioredIndex == 1 && spreadView.phase < 2) {
314+ if (root.focusFirstApp && behavioredIndex == 1 && spreadView.phase < 2) {
315 tileProgress += spreadView.tileDistance / spreadView.width;
316 }
317 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
318@@ -367,7 +395,7 @@
319
320 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
321 animatedProgress: {
322- if (spreadView.phase == 0 && index < 2) {
323+ if (spreadView.phase == 0 && index <= priv.firstSpreadIndex) {
324 if (progress < spreadView.positionMarker1) {
325 return progress;
326 } else if (progress < spreadView.positionMarker1 + 0.05){
327@@ -392,11 +420,11 @@
328 }
329
330 onClicked: {
331- if (spreadView.phase == 2) {
332- if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
333+ if (root.altTabEnabled && spreadView.phase == 2) {
334+ if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) {
335 spreadView.snapTo(index);
336 } else {
337- ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
338+ applicationManager.requestFocusApplication(applicationManager.get(index).appId);
339 }
340 }
341 }
342@@ -411,7 +439,7 @@
343
344 onClosed: {
345 spreadView.closingIndex = index;
346- ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
347+ applicationManager.stopApplication(applicationManager.get(index).appId);
348 }
349 }
350 }
351@@ -424,7 +452,7 @@
352 direction: Direction.Leftwards
353 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
354
355- anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
356+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
357 width: root.dragAreaWidth
358
359 property var gesturePoints: new Array()
360
361=== modified file 'qml/Stages/TransformedSpreadDelegate.qml'
362--- qml/Stages/TransformedSpreadDelegate.qml 2014-12-01 12:09:28 +0000
363+++ qml/Stages/TransformedSpreadDelegate.qml 2015-02-11 16:32:25 +0000
364@@ -46,6 +46,18 @@
365 property real startDistance: units.gu(5)
366 property real endDistance: units.gu(.5)
367
368+ // Whether first app is displayed entirely vs just on the spread edge
369+ property bool focusFirstApp: true
370+
371+ // Whether this delegate is displayed to the user outside of the spread
372+ readonly property bool isFocused: focusFirstApp && index === 0
373+
374+ // Adjusted index for any spread animation calculations
375+ readonly property int spreadIndex: index - (focusFirstApp ? 1 : 0)
376+
377+ // How far left this delegate has been translated
378+ readonly property alias xTranslate: priv.xTranslate
379+
380 onSelectedChanged: {
381 if (selected) {
382 priv.snapshot();
383@@ -63,8 +75,8 @@
384 Connections {
385 target: spreadView
386 onPhaseChanged: {
387- if (spreadView.phase == 1) {
388- if (index == 0) {
389+ if (root.focusFirstApp && spreadView.phase == 1) {
390+ if (index === 0) {
391 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
392 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
393 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
394@@ -134,7 +146,7 @@
395 }
396
397 if (otherSelected) {
398- if (spreadView.phase < 2 && index == 0) {
399+ if (spreadView.phase < 2 && root.isFocused) {
400 return linearAnimation(selectedProgress, 0, selectedXTranslate,
401 selectedXTranslate - spreadView.tileDistance, root.progress);
402 }
403@@ -142,8 +154,7 @@
404 return selectedXTranslate;
405 }
406
407- switch (index) {
408- case 0:
409+ if (root.focusFirstApp && index === 0) {
410 if (spreadView.phase == 0) {
411 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
412 0, -spreadView.width * .25, root.animatedProgress));
413@@ -153,11 +164,10 @@
414 } else if (!priv.isSelected){ // phase 2
415 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
416 return -easingCurve.value * spreadView.width + spreadView.width;
417- } else if (priv.isSelected) {
418+ } else {
419 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
420 }
421-
422- case 1:
423+ } else if (root.focusFirstApp && index === 1) {
424 if (spreadView.phase == 0 && !priv.isSelected) {
425 return linearAnimation(0, spreadView.positionMarker2,
426 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
427@@ -170,13 +180,13 @@
428
429 if (priv.isSelected) {
430 // Distance to left edge
431- var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
432+ var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance);
433 return linearAnimation(selectedProgress, 0,
434 selectedXTranslate, targetTranslate, root.progress);
435 }
436
437 // Fix it at the right edge...
438- var rightEdgeOffset = -((index - 1) * root.startDistance);
439+ var rightEdgeOffset = -(root.spreadIndex * root.startDistance);
440 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
441 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
442 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
443@@ -194,8 +204,7 @@
444 if (priv.isSelected) {
445 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
446 }
447- switch (index) {
448- case 0:
449+ if (root.focusFirstApp && index === 0) {
450 if (spreadView.phase == 0) {
451 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
452 0, root.tile0SnapAngle, root.animatedProgress));
453@@ -203,7 +212,7 @@
454 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
455 root.tile0SnapAngle, phase2startAngle, root.progress);
456 }
457- case 1:
458+ } else if (root.focusFirstApp && index === 1) {
459 if (spreadView.phase == 0) {
460 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
461 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
462@@ -227,15 +236,14 @@
463 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
464 }
465
466- switch (index) {
467- case 0:
468+ if (root.focusFirstApp && index === 0) {
469 if (spreadView.phase == 0) {
470 return 1;
471 } else if (spreadView.phase == 1) {
472 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
473 1, phase2startScale, root.progress);
474 }
475- case 1:
476+ } else if (root.focusFirstApp && index === 1) {
477 if (spreadView.phase == 0) {
478 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
479 return linearAnimation(0, spreadView.positionMarker2,
480@@ -254,7 +262,7 @@
481 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
482 selectedOpacity, 0, root.progress);
483 }
484- if (index == 0) {
485+ if (root.isFocused) {
486 switch (spreadView.phase) {
487 case 0:
488 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
489@@ -272,16 +280,14 @@
490 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
491 }
492
493- switch (index) {
494- case 0:
495+ if (root.focusFirstApp && index === 0) {
496 if (spreadView.phase == 0) {
497 return 0;
498 } else if (spreadView.phase == 1) {
499 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
500 0, priv.phase2startTopMarginProgress, root.progress);
501 }
502- break;
503- case 1:
504+ } else if (root.focusFirstApp && index === 1) {
505 if (spreadView.phase == 0) {
506 return 0;
507 } else if (spreadView.phase == 1) {
508
509=== modified file 'qml/Tutorial/Slider.qml'
510--- qml/Tutorial/Slider.qml 2015-02-11 16:32:24 +0000
511+++ qml/Tutorial/Slider.qml 2015-02-11 16:32:25 +0000
512@@ -38,7 +38,7 @@
513 readonly property real margin: units.gu(0.5)
514 readonly property real arrowSize: root.height - margin * 2
515 readonly property real dotSize: units.dp(1)
516- readonly property real slideOffset: Math.min(root.offset - offscreenOffset, target.x)
517+ readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
518 readonly property real offscreenOffset: units.gu(2)
519 }
520
521@@ -111,7 +111,13 @@
522 color: UbuntuColors.orange
523 darkenBy: root.active ? 0.5 : 0
524 anchors.left: parent.left
525- anchors.leftMargin: d.slideOffset
526+ // We use a Translate transform rather than anchors.leftMargin because
527+ // the latter has weird performance problems on the TutorialRight page.
528+ transform: [
529+ Translate {
530+ x: d.slideOffset
531+ }
532+ ]
533 anchors.verticalCenter: parent.verticalCenter
534 }
535 }
536
537=== added file 'qml/Tutorial/Tick.qml'
538--- qml/Tutorial/Tick.qml 1970-01-01 00:00:00 +0000
539+++ qml/Tutorial/Tick.qml 2015-02-11 16:32:25 +0000
540@@ -0,0 +1,29 @@
541+/*
542+ * Copyright (C) 2015 Canonical, Ltd.
543+ *
544+ * This program is free software; you can redistribute it and/or modify
545+ * it under the terms of the GNU General Public License as published by
546+ * the Free Software Foundation; version 3.
547+ *
548+ * This program is distributed in the hope that it will be useful,
549+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
550+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
551+ * GNU General Public License for more details.
552+ *
553+ * You should have received a copy of the GNU General Public License
554+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
555+ */
556+
557+import QtQuick 2.3
558+import Ubuntu.Components 1.1
559+
560+MouseArea {
561+ implicitHeight: tick.height
562+ implicitWidth: tick.width
563+ Image {
564+ id: tick
565+ source: Qt.resolvedUrl("graphics/tick.png")
566+ height: units.gu(6.5)
567+ width: units.gu(6.5)
568+ }
569+}
570
571=== modified file 'qml/Tutorial/Tutorial.qml'
572--- qml/Tutorial/Tutorial.qml 2015-02-11 16:32:24 +0000
573+++ qml/Tutorial/Tutorial.qml 2015-02-11 16:32:25 +0000
574@@ -22,6 +22,7 @@
575
576 property alias active: loader.active
577 property bool paused
578+ property real edgeSize
579
580 property Item launcher
581 property Item panel
582@@ -29,7 +30,7 @@
583 property Item overlay
584
585 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
586- readonly property bool stagesEnabled: loader.item ? loader.item.stagesEnabled : true
587+ readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
588 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
589 readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
590 readonly property bool running: loader.item ? loader.item.running : false
591@@ -55,6 +56,12 @@
592
593 Binding {
594 target: loader.item
595+ property: "edgeSize"
596+ value: root.edgeSize
597+ }
598+
599+ Binding {
600+ target: loader.item
601 property: "launcher"
602 value: root.launcher
603 }
604
605=== added file 'qml/Tutorial/TutorialBottom.qml'
606--- qml/Tutorial/TutorialBottom.qml 1970-01-01 00:00:00 +0000
607+++ qml/Tutorial/TutorialBottom.qml 2015-02-11 16:32:25 +0000
608@@ -0,0 +1,104 @@
609+/*
610+ * Copyright (C) 2014 Canonical, Ltd.
611+ *
612+ * This program is free software; you can redistribute it and/or modify
613+ * it under the terms of the GNU General Public License as published by
614+ * the Free Software Foundation; version 3.
615+ *
616+ * This program is distributed in the hope that it will be useful,
617+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
618+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
619+ * GNU General Public License for more details.
620+ *
621+ * You should have received a copy of the GNU General Public License
622+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
623+ */
624+
625+import QtQuick 2.3
626+import Ubuntu.Components 1.1
627+import Ubuntu.Gestures 0.1
628+import "../Components"
629+import "." as LocalComponents
630+
631+TutorialPage {
632+ id: root
633+
634+ property alias edgeSize: dragArea.height
635+
636+ title: i18n.tr("Open special menus")
637+ text: i18n.tr("Swipe up from the bottom edge.")
638+ fullTextWidth: true
639+
640+ SequentialAnimation {
641+ id: teaseAnimation
642+ paused: running && root.paused
643+ running: !dragArea.useTouchY && slider.dragOffset === 0
644+ loops: Animation.Infinite
645+
646+ UbuntuNumberAnimation {
647+ target: slider
648+ property: "teaseOffset"
649+ to: units.gu(1)
650+ duration: UbuntuAnimation.SleepyDuration
651+ }
652+ UbuntuNumberAnimation {
653+ target: slider
654+ property: "teaseOffset"
655+ to: 0
656+ duration: UbuntuAnimation.SleepyDuration
657+ }
658+ }
659+
660+ foreground {
661+ children: [
662+ LocalComponents.Slider {
663+ id: slider
664+ anchors {
665+ bottom: parent.bottom
666+ bottomMargin: width / 2 - height / 2
667+ horizontalCenter: parent.horizontalCenter
668+ }
669+ rotation: -90
670+ offset: teaseOffset + dragOffset
671+ active: dragArea.dragging
672+
673+ property real teaseOffset
674+ property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0
675+
676+ Behavior on dragOffset {
677+ id: offsetAnimation
678+ UbuntuNumberAnimation {}
679+ }
680+ }
681+ ]
682+ }
683+
684+ EdgeDragArea {
685+ id: dragArea
686+ direction: Direction.Upwards
687+ anchors {
688+ bottom: parent.bottom
689+ left: parent.left
690+ right: parent.right
691+ }
692+
693+ property bool useTouchY
694+
695+ onDraggingChanged: {
696+ if (!dragging) {
697+ if (slider.percent >= 0.85) {
698+ root.hide();
699+ } else if (slider.percent >= 0.15) {
700+ root.showError();
701+ }
702+ }
703+
704+ // We use a separate vars here rather than just directly looking at
705+ // 'dragging' because we want to preserve our 'slider.offset'
706+ // value during the above percent check. Now that we made it,
707+ // we can have 'slider.offset' go back to zero.
708+ offsetAnimation.enabled = !dragging;
709+ useTouchY = dragging;
710+ }
711+ }
712+}
713
714=== added file 'qml/Tutorial/TutorialBottomFinish.qml'
715--- qml/Tutorial/TutorialBottomFinish.qml 1970-01-01 00:00:00 +0000
716+++ qml/Tutorial/TutorialBottomFinish.qml 2015-02-11 16:32:25 +0000
717@@ -0,0 +1,41 @@
718+/*
719+ * Copyright (C) 2014 Canonical, Ltd.
720+ *
721+ * This program is free software; you can redistribute it and/or modify
722+ * it under the terms of the GNU General Public License as published by
723+ * the Free Software Foundation; version 3.
724+ *
725+ * This program is distributed in the hope that it will be useful,
726+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
727+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
728+ * GNU General Public License for more details.
729+ *
730+ * You should have received a copy of the GNU General Public License
731+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
732+ */
733+
734+import QtQuick 2.3
735+import Ubuntu.Components 1.1
736+import "." as LocalComponents
737+
738+TutorialPage {
739+ id: root
740+
741+ title: i18n.tr("This action does different things for different apps")
742+ text: i18n.tr("Tap here to finish.")
743+ fullTextWidth: true
744+
745+ foreground {
746+ children: [
747+ LocalComponents.Tick {
748+ objectName: "tick"
749+ anchors {
750+ horizontalCenter: parent.horizontalCenter
751+ top: parent.top
752+ topMargin: root.textBottom + units.gu(3)
753+ }
754+ onClicked: root.hide()
755+ }
756+ ]
757+ }
758+}
759
760=== modified file 'qml/Tutorial/TutorialContent.qml'
761--- qml/Tutorial/TutorialContent.qml 2015-02-11 16:32:24 +0000
762+++ qml/Tutorial/TutorialContent.qml 2015-02-11 16:32:25 +0000
763@@ -26,13 +26,14 @@
764 property Item overlay
765
766 readonly property bool launcherEnabled: !running ||
767- (!paused && loader.target === leftComponent)
768- readonly property bool stagesEnabled: !running
769+ (!paused && tutorialLeft.shown)
770+ readonly property bool spreadEnabled: !running
771 readonly property bool panelEnabled: !running
772 readonly property bool panelContentEnabled: !running
773- readonly property bool running: loader.sourceComponent !== null
774+ readonly property alias running: d.running
775
776 property bool paused: false
777+ property real edgeSize
778
779 signal finished()
780
781@@ -50,81 +51,74 @@
782 QtObject {
783 id: d
784
785+ property bool running
786+
787 function stop() {
788- loader.sourceComponent = null;
789+ running = false;
790 }
791
792 function start() {
793- loader.load(leftComponent);
794- }
795- }
796-
797- Loader {
798- id: loader
799- objectName: "tutorialLoader"
800-
801- property Component target: {
802- if (next) {
803- return next;
804- } else if (loader.item && loader.item.shown) {
805- return sourceComponent;
806- } else {
807- return null;
808- }
809- }
810-
811- property Component next: null
812-
813- function load(comp) {
814- if (loader.item) {
815- next = comp;
816- loader.item.hide();
817- } else {
818- loader.sourceComponent = comp;
819- }
820- }
821-
822- Connections {
823- target: loader.item
824- onFinished: {
825- loader.sourceComponent = loader.next;
826- if (loader.next != null) {
827- loader.next = null;
828- } else {
829- root.finished();
830- }
831- }
832- }
833-
834- Binding {
835- target: loader.item
836- property: "paused"
837- value: root.paused
838- }
839- }
840-
841- Component {
842- id: leftComponent
843- TutorialLeft {
844- objectName: "tutorialLeft"
845- parent: root.stages
846- anchors.fill: parent
847- launcher: root.launcher
848-
849- onFinished: loader.load(leftFinishComponent)
850- }
851- }
852-
853- Component {
854- id: leftFinishComponent
855- TutorialLeftFinish {
856- objectName: "tutorialLeftFinish"
857- parent: root.stages
858- anchors.fill: parent
859- textXOffset: root.launcher.panelWidth
860- backgroundFadesOut: true
861-
862- onFinished: root.launcher.hide()
863- }
864+ running = true;
865+ tutorialLeft.show();
866+ }
867+ }
868+
869+ TutorialLeft {
870+ id: tutorialLeft
871+ objectName: "tutorialLeft"
872+ parent: root.stages
873+ anchors.fill: parent
874+ launcher: root.launcher
875+ paused: !shown || root.paused
876+
877+ onFinished: tutorialLeftFinish.show()
878+ }
879+
880+ TutorialLeftFinish {
881+ id: tutorialLeftFinish
882+ objectName: "tutorialLeftFinish"
883+ parent: root.stages
884+ anchors.fill: parent
885+ textXOffset: root.launcher.panelWidth
886+ paused: !shown || root.paused
887+
888+ onFinished: {
889+ root.launcher.hide();
890+ tutorialRight.show();
891+ }
892+ }
893+
894+ TutorialRight {
895+ id: tutorialRight
896+ objectName: "tutorialRight"
897+ parent: root.stages
898+ anchors.fill: parent
899+ edgeSize: root.edgeSize
900+ panel: root.panel
901+ paused: !shown || root.paused
902+
903+ onFinished: tutorialBottom.show()
904+ }
905+
906+ TutorialBottom {
907+ id: tutorialBottom
908+ objectName: "tutorialBottom"
909+ parent: root.stages
910+ anchors.fill: parent
911+ edgeSize: root.edgeSize
912+ paused: !shown || root.paused
913+
914+ onFinished: tutorialBottomFinish.show()
915+ }
916+
917+ TutorialBottomFinish {
918+ id: tutorialBottomFinish
919+ objectName: "tutorialBottomFinish"
920+ parent: root.stages
921+ anchors.fill: parent
922+ backgroundFadesOut: true
923+ paused: !shown || root.paused
924+
925+ onFinished: root.finish()
926 }
927 }
928
929=== modified file 'qml/Tutorial/TutorialLeftFinish.qml'
930--- qml/Tutorial/TutorialLeftFinish.qml 2015-02-11 16:32:24 +0000
931+++ qml/Tutorial/TutorialLeftFinish.qml 2015-02-11 16:32:25 +0000
932@@ -16,31 +16,25 @@
933
934 import QtQuick 2.3
935 import Ubuntu.Components 1.1
936+import "." as LocalComponents
937
938 TutorialPage {
939 id: root
940
941 title: i18n.tr("These are the shortcuts to favorite apps")
942- text: i18n.tr("Tap here to finish.")
943+ text: i18n.tr("Tap here to continue.")
944 fullTextWidth: true
945
946 foreground {
947 children: [
948- Image {
949+ LocalComponents.Tick {
950 objectName: "tick"
951 anchors {
952 horizontalCenter: parent.horizontalCenter
953 top: parent.top
954 topMargin: root.textBottom + units.gu(3)
955 }
956- source: Qt.resolvedUrl("graphics/tick.png")
957- height: units.gu(6.5)
958- width: units.gu(6.5)
959-
960- MouseArea {
961- anchors.fill: parent
962- onClicked: root.hide()
963- }
964+ onClicked: root.hide()
965 }
966 ]
967 }
968
969=== modified file 'qml/Tutorial/TutorialPage.qml'
970--- qml/Tutorial/TutorialPage.qml 2015-02-11 16:32:24 +0000
971+++ qml/Tutorial/TutorialPage.qml 2015-02-11 16:32:25 +0000
972@@ -52,8 +52,8 @@
973 property real textXOffset: 0
974 property real textYOffset: 0
975
976- // Foreground opacity
977- property real foregroundOpacity: 1
978+ // Foreground text opacity
979+ property real textOpacity: 1
980
981 signal finished()
982
983@@ -63,8 +63,8 @@
984
985 ////
986
987+ visible: false
988 shown: false
989- Component.onCompleted: show()
990
991 property real _foregroundHideOpacity
992
993@@ -73,16 +73,16 @@
994 from: 0
995 to: 1
996 duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
997+ onStarted: root.visible = true
998 }
999
1000 hideAnimation: StandardAnimation {
1001 property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
1002 to: 0
1003 duration: UbuntuAnimation.BriskDuration
1004- onRunningChanged: {
1005- if (!running) {
1006- root.finished();
1007- }
1008+ onStopped: {
1009+ root.visible = false;
1010+ root.finished();
1011 }
1012 }
1013
1014@@ -122,73 +122,78 @@
1015 Item {
1016 id: foreground
1017 anchors.fill: parent
1018- opacity: root.foregroundOpacity < 1 ? root.foregroundOpacity : root._foregroundHideOpacity
1019-
1020- Label {
1021- id: titleLabel
1022- anchors {
1023- top: parent.verticalCenter
1024- topMargin: d.verticalOffset + root.textYOffset
1025- left: parent.left
1026- leftMargin: d.sideMargin + d.textXOffset
1027- }
1028- width: parent.width - d.sideMargin * 2
1029- horizontalAlignment: Text.AlignLeft
1030- wrapMode: Text.Wrap
1031- font.weight: Font.Light
1032- font.pixelSize: units.gu(3.5)
1033- }
1034-
1035- Label {
1036- id: textLabel
1037- anchors {
1038- top: titleLabel.bottom
1039- topMargin: units.gu(2)
1040- left: parent.left
1041- leftMargin: d.sideMargin + d.textXOffset
1042- }
1043- width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
1044- horizontalAlignment: Text.AlignLeft
1045- wrapMode: Text.Wrap
1046- font.weight: Font.Light
1047- font.pixelSize: units.gu(2.5)
1048- }
1049-
1050- // We use two separate labels like this rather than just changing
1051- // the text of the above labels because we want to know where to place
1052- // sliders (via root.textBottom) without having that place change
1053- // as the text changes length.
1054- Label {
1055- id: errorTitleLabel
1056- objectName: "errorTitleLabel"
1057- anchors {
1058- top: titleLabel.top
1059- left: titleLabel.left
1060- }
1061- width: titleLabel.width
1062- horizontalAlignment: titleLabel.horizontalAlignment
1063- wrapMode: titleLabel.wrapMode
1064- font.weight: titleLabel.font.weight
1065- font.pixelSize: titleLabel.font.pixelSize
1066- opacity: 0
1067- text: i18n.tr("You almost got it!")
1068- }
1069-
1070- Label {
1071- id: errorTextLabel
1072- objectName: "errorTextLabel"
1073- anchors {
1074- top: errorTitleLabel.bottom
1075- topMargin: textLabel.anchors.topMargin
1076- left: textLabel.left
1077- }
1078- width: textLabel.width
1079- horizontalAlignment: textLabel.horizontalAlignment
1080- wrapMode: textLabel.wrapMode
1081- font.weight: textLabel.font.weight
1082- font.pixelSize: textLabel.font.pixelSize
1083- opacity: 0
1084- text: i18n.tr("Try again.")
1085+ opacity: root._foregroundHideOpacity
1086+
1087+ Item {
1088+ anchors.fill: parent
1089+ opacity: root.textOpacity
1090+
1091+ Label {
1092+ id: titleLabel
1093+ anchors {
1094+ top: parent.verticalCenter
1095+ topMargin: d.verticalOffset + root.textYOffset
1096+ left: parent.left
1097+ leftMargin: d.sideMargin + d.textXOffset
1098+ }
1099+ width: parent.width - d.sideMargin * 2
1100+ horizontalAlignment: Text.AlignLeft
1101+ wrapMode: Text.Wrap
1102+ font.weight: Font.Light
1103+ font.pixelSize: units.gu(3.5)
1104+ }
1105+
1106+ Label {
1107+ id: textLabel
1108+ anchors {
1109+ top: titleLabel.bottom
1110+ topMargin: units.gu(2)
1111+ left: parent.left
1112+ leftMargin: d.sideMargin + d.textXOffset
1113+ }
1114+ width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
1115+ horizontalAlignment: Text.AlignLeft
1116+ wrapMode: Text.Wrap
1117+ font.weight: Font.Light
1118+ font.pixelSize: units.gu(2.5)
1119+ }
1120+
1121+ // We use two separate labels like this rather than just changing
1122+ // the text of the above labels because we want to know where to place
1123+ // sliders (via root.textBottom) without having that place change
1124+ // as the text changes length.
1125+ Label {
1126+ id: errorTitleLabel
1127+ objectName: "errorTitleLabel"
1128+ anchors {
1129+ top: titleLabel.top
1130+ left: titleLabel.left
1131+ }
1132+ width: titleLabel.width
1133+ horizontalAlignment: titleLabel.horizontalAlignment
1134+ wrapMode: titleLabel.wrapMode
1135+ font.weight: titleLabel.font.weight
1136+ font.pixelSize: titleLabel.font.pixelSize
1137+ opacity: 0
1138+ text: i18n.tr("You almost got it!")
1139+ }
1140+
1141+ Label {
1142+ id: errorTextLabel
1143+ objectName: "errorTextLabel"
1144+ anchors {
1145+ top: errorTitleLabel.bottom
1146+ topMargin: textLabel.anchors.topMargin
1147+ left: textLabel.left
1148+ }
1149+ width: textLabel.width
1150+ horizontalAlignment: textLabel.horizontalAlignment
1151+ wrapMode: textLabel.wrapMode
1152+ font.weight: textLabel.font.weight
1153+ font.pixelSize: textLabel.font.pixelSize
1154+ opacity: 0
1155+ text: i18n.tr("Try again.")
1156+ }
1157 }
1158
1159 // A place for subclasses to add extra widgets
1160
1161=== added file 'qml/Tutorial/TutorialRight.qml'
1162--- qml/Tutorial/TutorialRight.qml 1970-01-01 00:00:00 +0000
1163+++ qml/Tutorial/TutorialRight.qml 2015-02-11 16:32:25 +0000
1164@@ -0,0 +1,223 @@
1165+/*
1166+ * Copyright (C) 2014 Canonical, Ltd.
1167+ *
1168+ * This program is free software; you can redistribute it and/or modify
1169+ * it under the terms of the GNU General Public License as published by
1170+ * the Free Software Foundation; version 3.
1171+ *
1172+ * This program is distributed in the hope that it will be useful,
1173+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1174+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1175+ * GNU General Public License for more details.
1176+ *
1177+ * You should have received a copy of the GNU General Public License
1178+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1179+ */
1180+
1181+import QtQuick 2.3
1182+import Ubuntu.Components 1.1
1183+import Ubuntu.Gestures 0.1
1184+import Unity.Application 0.1
1185+import "../Components"
1186+import "../Stages"
1187+import "." as LocalComponents
1188+
1189+TutorialPage {
1190+ id: root
1191+
1192+ property var panel
1193+ property alias edgeSize: stage.dragAreaWidth
1194+
1195+ title: i18n.tr("To view open apps")
1196+ text: i18n.tr("Long swipe from the right edge.")
1197+
1198+ textOpacity: 1 - slider.percent
1199+
1200+ SequentialAnimation {
1201+ id: teaseAnimation
1202+ paused: running && root.paused
1203+ running: !stage.dragging && stage.dragProgress === 0
1204+ loops: Animation.Infinite
1205+
1206+ UbuntuNumberAnimation {
1207+ target: stage
1208+ property: "x"
1209+ to: -stage.dragAreaOverlap
1210+ duration: UbuntuAnimation.SleepyDuration
1211+ }
1212+ UbuntuNumberAnimation {
1213+ target: stage
1214+ property: "x"
1215+ to: 0
1216+ duration: UbuntuAnimation.SleepyDuration
1217+ }
1218+ }
1219+
1220+ foreground {
1221+ children: [
1222+ LocalComponents.Slider {
1223+ id: slider
1224+ anchors {
1225+ right: parent.right
1226+ top: parent.top
1227+ topMargin: root.textBottom + units.gu(3)
1228+ }
1229+ rotation: 180
1230+ offset: stage.dragProgress - stage.x
1231+ active: stage.dragging
1232+ },
1233+
1234+ // Just assume PhoneStage for now. The tablet version of the right-edge
1235+ // tutorial is still being spec'd by the design team.
1236+ PhoneStage {
1237+ id: stage
1238+ objectName: "stage"
1239+ anchors.top: parent.top
1240+ width: parent.width
1241+ height: parent.height
1242+ applicationManager: fakeAppManager
1243+ color: "transparent"
1244+ interactive: false
1245+ altTabEnabled: false
1246+ focusFirstApp: false
1247+ startScale: 0.8
1248+ endScale: 0.6
1249+ dragAreaOverlap: units.gu(2)
1250+
1251+ onOpened: {
1252+ overlay.show();
1253+ root.textOpacity = 0;
1254+ slider.visible = false;
1255+ }
1256+
1257+ onDraggingChanged: {
1258+ if (!dragging) {
1259+ if (!overlay.shown) {
1260+ root.showError();
1261+ }
1262+ teaseAnimation.complete();
1263+ }
1264+ }
1265+ },
1266+
1267+ Showable {
1268+ id: overlay
1269+ objectName: "overlay"
1270+ anchors.fill: parent
1271+
1272+ opacity: 0
1273+ shown: false
1274+ showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 }
1275+
1276+ Label {
1277+ anchors.top: parent.top
1278+ anchors.topMargin: root.panel.panelHeight + units.gu(2)
1279+ anchors.left: parent.left
1280+ anchors.leftMargin: units.gu(2)
1281+ anchors.right: parent.right
1282+ anchors.rightMargin: units.gu(2)
1283+ wrapMode: Text.Wrap
1284+ horizontalAlignment: Text.AlignHCenter
1285+ fontSize: "large"
1286+ text: i18n.tr("View all your running tasks.")
1287+ }
1288+
1289+ LocalComponents.Tick {
1290+ objectName: "tick"
1291+ anchors.bottom: bottomOverlayText.top
1292+ anchors.bottomMargin: units.gu(1)
1293+ anchors.horizontalCenter: bottomOverlayText.horizontalCenter
1294+ onClicked: root.hide()
1295+ }
1296+
1297+ Label {
1298+ id: bottomOverlayText
1299+ anchors.bottom: parent.bottom
1300+ anchors.bottomMargin: units.gu(2)
1301+ anchors.left: parent.left
1302+ anchors.leftMargin: units.gu(2)
1303+ anchors.right: parent.right
1304+ anchors.rightMargin: units.gu(2)
1305+ wrapMode: Text.Wrap
1306+ horizontalAlignment: Text.AlignHCenter
1307+ fontSize: "small"
1308+ text: i18n.tr("Tap here to continue.")
1309+ }
1310+ }
1311+ ]
1312+ }
1313+
1314+ ListModel {
1315+ id: fakeAppManager
1316+
1317+ readonly property string focusedApplicationId: "facebook"
1318+
1319+ function focusApplication(appId) {}
1320+ function requestFocusApplication(appId) {}
1321+ function findApplication(appId) {return null;}
1322+
1323+ signal applicationAdded(string appId)
1324+ signal applicationRemoved(string appId)
1325+ signal focusRequested(string appId)
1326+
1327+ ListElement {
1328+ appId: "facebook"
1329+ fullscreen: false
1330+ name: ""
1331+ icon: ""
1332+ state: ApplicationInfoInterface.Stopped
1333+ splashTitle: ""
1334+ splashImage: ""
1335+ splashShowHeader: false
1336+ splashColor: "transparent"
1337+ splashColorHeader: "transparent"
1338+ splashColorFooter: "transparent"
1339+ defaultScreenshot: "../Tutorial/graphics/facebook.png"
1340+ }
1341+
1342+ ListElement {
1343+ appId: "camera"
1344+ fullscreen: false
1345+ name: ""
1346+ icon: ""
1347+ state: ApplicationInfoInterface.Stopped
1348+ splashTitle: ""
1349+ splashImage: ""
1350+ splashShowHeader: false
1351+ splashColor: "transparent"
1352+ splashColorHeader: "transparent"
1353+ splashColorFooter: "transparent"
1354+ defaultScreenshot: "../Tutorial/graphics/camera.png"
1355+ }
1356+
1357+ ListElement {
1358+ appId: "gallery"
1359+ fullscreen: false
1360+ name: ""
1361+ icon: ""
1362+ state: ApplicationInfoInterface.Stopped
1363+ splashTitle: ""
1364+ splashImage: ""
1365+ splashShowHeader: false
1366+ splashColor: "transparent"
1367+ splashColorHeader: "transparent"
1368+ splashColorFooter: "transparent"
1369+ defaultScreenshot: "../Tutorial/graphics/gallery.png"
1370+ }
1371+
1372+ ListElement {
1373+ appId: "dialer"
1374+ fullscreen: false
1375+ name: ""
1376+ icon: ""
1377+ state: ApplicationInfoInterface.Stopped
1378+ splashTitle: ""
1379+ splashImage: ""
1380+ splashShowHeader: false
1381+ splashColor: "transparent"
1382+ splashColorHeader: "transparent"
1383+ splashColorFooter: "transparent"
1384+ defaultScreenshot: "../Tutorial/graphics/dialer.png"
1385+ }
1386+ }
1387+}
1388
1389=== added file 'qml/Tutorial/graphics/camera.png'
1390Binary files qml/Tutorial/graphics/camera.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/camera.png 2015-02-11 16:32:25 +0000 differ
1391=== added file 'qml/Tutorial/graphics/dialer.png'
1392Binary files qml/Tutorial/graphics/dialer.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/dialer.png 2015-02-11 16:32:25 +0000 differ
1393=== added file 'qml/Tutorial/graphics/facebook.png'
1394Binary files qml/Tutorial/graphics/facebook.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/facebook.png 2015-02-11 16:32:25 +0000 differ
1395=== added file 'qml/Tutorial/graphics/gallery.png'
1396Binary files qml/Tutorial/graphics/gallery.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/gallery.png 2015-02-11 16:32:25 +0000 differ
1397=== modified file 'tests/autopilot/unity8/shell/emulators/tutorial.py'
1398--- tests/autopilot/unity8/shell/emulators/tutorial.py 2015-02-11 16:32:24 +0000
1399+++ tests/autopilot/unity8/shell/emulators/tutorial.py 2015-02-11 16:32:25 +0000
1400@@ -35,20 +35,40 @@
1401 @classmethod
1402 def validate_dbus_object(cls, path, state):
1403 name = introspection.get_classname_from_path(path)
1404- return name == b'TutorialPage' or name == b'TutorialLeft'
1405+ return name in (b'TutorialPage', b'TutorialLeft',
1406+ b'TutorialLeftFinish', b'TutorialRight',
1407+ b'TutorialBottom', b'TutorialBottomFinish')
1408
1409 @autopilot.logging.log_action(logger.info)
1410 def short_swipe_right(self):
1411+ self.shown.wait_for(True)
1412 x, y, width, height = self.globalRect
1413 start_x = x
1414 stop_x = x + width // 3
1415 start_y = stop_y = y + height // 2
1416 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1417- self.shown.wait_for(False)
1418+
1419+ @autopilot.logging.log_action(logger.info)
1420+ def swipe_left(self):
1421+ self.shown.wait_for(True)
1422+ x, y, width, height = self.globalRect
1423+ start_x = width
1424+ stop_x = x
1425+ start_y = stop_y = y + height // 2
1426+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1427+
1428+ @autopilot.logging.log_action(logger.info)
1429+ def swipe_up(self):
1430+ self.shown.wait_for(True)
1431+ x, y, width, height = self.globalRect
1432+ start_y = height
1433+ stop_y = y
1434+ start_x = stop_x = x + width // 2
1435+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1436
1437 @autopilot.logging.log_action(logger.info)
1438 def tap(self):
1439 """Tap the tick button to complete this step."""
1440- button = self.select_single(objectName="tick")
1441+ self.shown.wait_for(True)
1442+ button = self.wait_select_single(objectName="tick")
1443 self.pointing_device.click_object(button)
1444- self.shown.wait_for(False)
1445
1446=== modified file 'tests/autopilot/unity8/shell/tests/test_tutorial.py'
1447--- tests/autopilot/unity8/shell/tests/test_tutorial.py 2015-02-11 16:32:24 +0000
1448+++ tests/autopilot/unity8/shell/tests/test_tutorial.py 2015-02-11 16:32:25 +0000
1449@@ -47,4 +47,11 @@
1450 page.short_swipe_right()
1451 page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
1452 page.tap()
1453+ page = self.unity.wait_select_single(objectName='tutorialRight')
1454+ page.swipe_left()
1455+ page.tap()
1456+ page = self.unity.wait_select_single(objectName='tutorialBottom')
1457+ page.swipe_up()
1458+ page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
1459+ page.tap()
1460 self.assertThat(tutorial.running, Eventually(Equals(False)))
1461
1462=== modified file 'tests/qmltests/Tutorial/tst_Tutorial.qml'
1463--- tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-11 16:32:24 +0000
1464+++ tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-11 16:32:25 +0000
1465@@ -162,7 +162,6 @@
1466 }
1467
1468 function waitForPage(name) {
1469- tryCompareFunction(function() { return findChild(shell, name) !== null; }, true);
1470 waitForRendering(findChild(shell, name));
1471 var page = findChild(shell, name);
1472 tryCompare(page, "shown", true);
1473@@ -221,6 +220,33 @@
1474 var tick = findChild(page, "tick");
1475 tap(tick);
1476
1477+ page = waitForPage("tutorialRight");
1478+ checkTopEdge();
1479+ checkLeftEdge();
1480+ checkBottomEdge();
1481+ if (name === "tutorialRight") return page;
1482+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
1483+ var overlay = findChild(page, "overlay");
1484+ tryCompare(overlay, "shown", true);
1485+ var tick = findChild(page, "tick");
1486+ tap(tick);
1487+
1488+ var page = waitForPage("tutorialBottom");
1489+ checkTopEdge();
1490+ checkLeftEdge();
1491+ checkRightEdge();
1492+ if (name === "tutorialBottom") return page;
1493+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
1494+
1495+ var page = waitForPage("tutorialBottomFinish");
1496+ checkTopEdge();
1497+ checkLeftEdge();
1498+ checkRightEdge();
1499+ checkBottomEdge();
1500+ if (name === "tutorialBottomFinish") return page;
1501+ var tick = findChild(page, "tick");
1502+ tap(tick);
1503+
1504 checkFinished();
1505 return null;
1506 }
1507@@ -277,6 +303,42 @@
1508 tryCompare(left, "shown", true); // and we should still be on left
1509 }
1510
1511+ function test_spread() {
1512+ // Unfortunately, most of what we want to test of the spread is
1513+ // "did it render correctly?" but that's hard to test. So instead,
1514+ // just poke and prod it a little bit to see if some of the values
1515+ // we'd expect to be correct, are so.
1516+
1517+ var right = goToPage("tutorialRight");
1518+ var stage = findChild(right, "stage");
1519+ var delegate0 = findChild(right, "appDelegate0");
1520+
1521+ tryCompare(stage, "dragProgress", 0);
1522+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
1523+ verify(stage.dragProgress > 0);
1524+ compare(stage.dragProgress, -delegate0.xTranslate);
1525+ touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
1526+ tryCompare(stage, "dragProgress", 0);
1527+
1528+ tryCompare(delegate0, "x", shell.width);
1529+
1530+ var screenshotImage = findChild(right, "screenshotImage");
1531+ tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
1532+ tryCompare(screenshotImage, "visible", true);
1533+ }
1534+
1535+ function test_bottomShortDrag() {
1536+ var bottom = goToPage("tutorialBottom");
1537+
1538+ touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
1539+
1540+ var errorTextLabel = findChild(bottom, "errorTextLabel");
1541+ var errorTitleLabel = findChild(bottom, "errorTitleLabel");
1542+ tryCompare(bottom, "shown", true); // still on bottom page
1543+ tryCompare(errorTextLabel, "opacity", 1); // show error
1544+ tryCompare(errorTitleLabel, "opacity", 1); // show error
1545+ }
1546+
1547 function test_interrupted() {
1548 goToPage("tutorialLeft");
1549 ApplicationManager.startApplication("dialer-app");

Subscribers

People subscribed via source and target branches