Merge lp:~mterry/unity8/tutorial-refactor into lp:unity8

Proposed by Michael Terry
Status: Merged
Approved by: Michael Terry
Approved revision: 1403
Merged at revision: 1603
Proposed branch: lp:~mterry/unity8/tutorial-refactor
Merge into: lp:unity8
Prerequisite: lp:~dandrader/unity8/fixSurfaceActiveFocus
Diff against target: 2307 lines (+1144/-811)
20 files modified
debian/unity8.install (+1/-0)
qml/CMakeLists.txt (+3/-2)
qml/Components/EdgeDemo.qml (+0/-254)
qml/Components/EdgeDemoOverlay.qml (+0/-273)
qml/Launcher/Launcher.qml (+13/-7)
qml/Shell.qml (+33/-23)
qml/Tutorial/Arrow.qml (+56/-0)
qml/Tutorial/Slider.qml (+117/-0)
qml/Tutorial/Tutorial.qml (+85/-0)
qml/Tutorial/TutorialContent.qml (+130/-0)
qml/Tutorial/TutorialLeft.qml (+91/-0)
qml/Tutorial/TutorialLeftFinish.qml (+47/-0)
qml/Tutorial/TutorialPage.qml (+240/-0)
tests/autopilot/unity8/shell/emulators/tutorial.py (+16/-102)
tests/autopilot/unity8/shell/fixture_setup.py (+6/-6)
tests/autopilot/unity8/shell/tests/test_tutorial.py (+17/-18)
tests/qmltests/CMakeLists.txt (+1/-1)
tests/qmltests/Components/tst_EdgeDemoOverlay.qml (+0/-123)
tests/qmltests/Tutorial/tst_Tutorial.qml (+286/-0)
tests/qmltests/tst_TabletShell.qml (+2/-2)
To merge this branch: bzr merge lp:~mterry/unity8/tutorial-refactor
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Pending
Andrea Cimitan Pending
Review via email: mp+249359@code.launchpad.net

This proposal supersedes a proposal from 2014-10-28.

Commit message

Redesign tutorial to match latest spec (just removing obsolete pages and redesigning look, no new pages yet)

Specifically, both the greeter and top-edge pages are dropped. Design decided they were self-explanatory enough that they didn't need to be part of the tutorial (in an effort to make it shorter).

The tutorial is still missing right-edge and bottom-edge pages. But since those are new, I am going to do them as a separate branch. This branch is just about trimming the tutorial and redesigning the look and feel.

This leaves us with a very short tutorial right now, but I'm OK with that. It will get longer in a moment with my next tutorial branch.

I did a whole refactor of the tutorial code while here. I didn't like the old way I had done it (too procedural rather than declarative).

I've also taken this opportunity to sync our technical name for the tutorial (edge demo) to what Design calls it (tutorial).

Description of the change

Redesign tutorial to match latest spec (just removing obsolete pages and redesigning look, no new pages yet)

Specifically, both the greeter and top-edge pages are dropped. Design decided they were self-explanatory enough that they didn't need to be part of the tutorial (in an effort to make it shorter).

The tutorial is still missing right-edge and bottom-edge pages. But since those are new, I am going to do them as a separate branch. This branch is just about trimming the tutorial and redesigning the look and feel.

This leaves us with a very short tutorial right now, but I'm OK with that. It will get longer in a moment with my next tutorial branch.

I did a whole refactor of the tutorial code while here. I didn't like the old way I had done it (too procedural rather than declarative).

I've also taken this opportunity to sync our technical name for the tutorial (edge demo) to what Design calls it (tutorial).

Here is the spec from Design:
https://docs.google.com/a/canonical.com/presentation/d/1M8S1wTo-5lIFaIxxgls9TCEnvR79XThGEVpRtkHJY_A

== Checklist ==

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

 * 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?
 I'm in that team

 * If you changed the UI, has there been a design review?
 Yes-ish, they saw an earlier version, will get final approval.

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
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in qml/Shell.qml
Conflict adding file qml/Tutorial. Moved existing file to qml/Tutorial.moved.
Text conflict in qml/Tutorial/Tutorial.qml
3 conflicts encountered.

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

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

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: Approve (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: Approve (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: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

I've triggered a rebuild, don't think the failures are because of this branch.

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

Ha! That was bad timing. I've cancelled the rebuild :D

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

And my cancelled build appears as failed :/

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Andrea Cimitan (cimi) wrote : Posted in a previous version of this proposal

I tested this few times in a row, until I found my launcher stuck 2px on screen when hidden (like always on screen 2px).
Might be some of the changes to Launcher.qml

another small thing, more a question, you added both chevron and tick png, but while for tick you used gridUnit, you used pixels for chevron... should both scale or not?

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

I'll look at the 2px-launcher problem.

As for gridUnit vs pixels... Arrow.qml mentions the size of the "chevron.png" source image in pixels, but for its actual height/width, it scales to whatever size the Arrow object is. So they both scale fine, I believe.

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

OK, fixed the launcher sometimes being left exposed. Thanks for the catch!

Revision history for this message
Andrea Cimitan (cimi) 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?
y
 * Did CI run pass? If not, please explain why.
y
 * Did you make sure that the branch does not contain spurious tags?
y

thanks, no issues I noticed now!

review: Approve
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 Terry (mterry) wrote : Posted in a previous version of this proposal

I'm going to wait to top-approve this, because design is still doing some minor tweaks (color tweaks, size of text, etc)

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
Michael Terry (mterry) wrote : Posted in a previous version of this proposal

OK, so this got updated with a couple minor design tweaks and a fix for the autopilot tests. Design has signed off on this version. And the autopilot passes now (well, you can't see it here yet -- I just kicked a rebuild, but you can see it in https://code.launchpad.net/~mterry/unity8/tutorial-new-screens/+merge/245699).

So I'm inclined to top-approve again if there isn't an objection.

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: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/unity8.install'
2--- debian/unity8.install 2014-11-14 22:40:00 +0000
3+++ debian/unity8.install 2015-02-11 17:00:33 +0000
4@@ -10,5 +10,6 @@
5 usr/share/unity8/Panel
6 usr/share/unity8/Shell.qml
7 usr/share/unity8/Stages
8+usr/share/unity8/Tutorial
9 usr/share/unity8/Wizard
10 usr/share/url-dispatcher/urls/unity8-dash.url-dispatcher
11
12=== modified file 'qml/CMakeLists.txt'
13--- qml/CMakeLists.txt 2014-11-14 23:30:10 +0000
14+++ qml/CMakeLists.txt 2015-02-11 17:00:33 +0000
15@@ -9,10 +9,11 @@
16 Dash
17 graphics
18 Greeter
19+ Launcher
20+ Notifications
21 Panel
22- Launcher
23 Stages
24- Notifications
25+ Tutorial
26 Wizard
27 )
28
29
30=== removed file 'qml/Components/EdgeDemo.qml'
31--- qml/Components/EdgeDemo.qml 2014-11-19 19:05:35 +0000
32+++ qml/Components/EdgeDemo.qml 1970-01-01 00:00:00 +0000
33@@ -1,254 +0,0 @@
34-/*
35- * Copyright (C) 2013 Canonical, Ltd.
36- *
37- * This program is free software; you can redistribute it and/or modify
38- * it under the terms of the GNU General Public License as published by
39- * the Free Software Foundation; version 3.
40- *
41- * This program is distributed in the hope that it will be useful,
42- * but WITHOUT ANY WARRANTY; without even the implied warranty of
43- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44- * GNU General Public License for more details.
45- *
46- * You should have received a copy of the GNU General Public License
47- * along with this program. If not, see <http://www.gnu.org/licenses/>.
48- */
49-
50-import AccountsService 0.1
51-import LightDM 0.1 as LightDM
52-import QtQuick 2.0
53-import Ubuntu.Components 1.1
54-
55-Item {
56- id: demo
57-
58- property Item greeter
59- property Item launcher
60- property Item panel
61- property Item stages
62-
63- property bool launcherEnabled: true
64- property bool stagesEnabled: true
65- property bool panelEnabled: true
66- property bool panelContentEnabled: true
67- property bool running: !launcherEnabled || !stagesEnabled || !panelEnabled || !panelContentEnabled
68-
69- property bool paused: false
70-
71- onPausedChanged: {
72- if (d.rightEdgeDemo) d.rightEdgeDemo.paused = paused
73- if (d.topEdgeDemo) d.topEdgeDemo.paused = paused
74- if (d.bottomEdgeDemo) d.bottomEdgeDemo.paused = paused
75- if (d.leftEdgeDemo) d.leftEdgeDemo.paused = paused
76- if (d.finalEdgeDemo) d.finalEdgeDemo.paused = paused
77- }
78-
79- function hideEdgeDemoInShell() {
80- AccountsService.demoEdges = false;
81- stopDemo();
82- }
83-
84- function hideEdgeDemoInGreeter() {
85- // TODO: AccountsService.demoEdges = false as lightdm user
86- }
87-
88- function hideEdgeDemos() {
89- hideEdgeDemoInGreeter();
90- hideEdgeDemoInShell();
91- }
92-
93- function stopDemo() {
94- launcherEnabled = true
95- stagesEnabled = true
96- panelEnabled = true
97- panelContentEnabled = true
98-
99- // Use a tiny delay for these destroy() calls because if a lot is
100- // happening at once (like creating and being destroyed in same event
101- // loop, as might happen when answering a call while demo is open),
102- // the destroy() call will be ignored.
103- if (d.rightEdgeDemo) d.rightEdgeDemo.destroy(1);
104- if (d.topEdgeDemo) d.topEdgeDemo.destroy(1);
105- if (d.bottomEdgeDemo) d.bottomEdgeDemo.destroy(1);
106- if (d.leftEdgeDemo) d.leftEdgeDemo.destroy(1);
107- if (d.finalEdgeDemo) d.finalEdgeDemo.destroy(1);
108- }
109-
110- function startDemo() {
111- if (!d.overlay) {
112- d.overlay = Qt.createComponent("EdgeDemoOverlay.qml")
113- }
114-
115- launcherEnabled = false;
116- stagesEnabled = false;
117- panelEnabled = false;
118- panelContentEnabled = false;
119-
120- // Begin with either greeter or top, depending on which is visible
121- if (greeter && greeter.shown) {
122- startRightEdgeDemo()
123- } else {
124- startTopEdgeDemo()
125- }
126- }
127-
128- QtObject {
129- id: d
130- property Component overlay
131- property QtObject rightEdgeDemo
132- property QtObject topEdgeDemo
133- property QtObject bottomEdgeDemo
134- property QtObject leftEdgeDemo
135- property QtObject finalEdgeDemo
136- property bool showEdgeDemo: AccountsService.demoEdges
137- property bool showEdgeDemoInGreeter: AccountsService.demoEdges // TODO: AccountsService.demoEdges as lightdm user
138-
139- function restartDemo() {
140- stopDemo()
141- if (d.showEdgeDemo) {
142- startDemo()
143- }
144- }
145-
146- onShowEdgeDemoChanged: restartDemo()
147- }
148-
149- Connections {
150- target: i18n
151- onLanguageChanged: d.restartDemo()
152- }
153-
154- function startRightEdgeDemo() {
155- if (demo.greeter) {
156- d.rightEdgeDemo = d.overlay.createObject(demo.greeter, {
157- "edge": "right",
158- "title": i18n.tr("Right edge"),
159- "text": i18n.tr("Try swiping from the right edge to unlock the phone"),
160- "anchors.fill": demo.greeter,
161- });
162- }
163- if (d.rightEdgeDemo) {
164- d.rightEdgeDemo.onSkip.connect(demo.hideEdgeDemos)
165- } else {
166- stopDemo();
167- }
168- }
169-
170- Connections {
171- target: demo.greeter
172-
173- function hide() {
174- if (d.rightEdgeDemo && d.rightEdgeDemo.available) {
175- d.rightEdgeDemo.hide()
176- hideEdgeDemoInGreeter()
177- startTopEdgeDemo()
178- }
179- }
180-
181- onUnlocked: hide()
182- onShownChanged: if (!greeter.shown) hide()
183- }
184-
185- function startTopEdgeDemo() {
186- demo.panelEnabled = true;
187- if (demo.stages) {
188- d.topEdgeDemo = d.overlay.createObject(demo.panel, {
189- "edge": "top",
190- "title": i18n.tr("Top edge"),
191- "text": i18n.tr("Try swiping from the top edge to access the indicators"),
192- "anchors.fill": demo.panel,
193- });
194- }
195- if (d.topEdgeDemo) {
196- d.topEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
197- } else {
198- stopDemo();
199- }
200- }
201-
202- Connections {
203- target: demo.panel.indicators
204- onFullyOpenedChanged: {
205- if (d.topEdgeDemo && d.topEdgeDemo.available && demo.panel.indicators.fullyOpened) {
206- d.topEdgeDemo.hideNow()
207- startBottomEdgeDemo()
208- }
209- }
210- }
211-
212- function startBottomEdgeDemo() {
213- if (demo.panel.indicators) {
214- d.bottomEdgeDemo = d.overlay.createObject(demo.panel.indicators, {
215- "edge": "bottom",
216- "title": i18n.tr("Close"),
217- "text": i18n.tr("Swipe up again to close the settings screen"),
218- "anchors.fill": demo.panel.indicators,
219- });
220- }
221- if (d.bottomEdgeDemo) {
222- d.bottomEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
223- } else {
224- stopDemo();
225- }
226- }
227-
228- Connections {
229- target: demo.panel.indicators
230- onPartiallyOpenedChanged: {
231- if (d.bottomEdgeDemo &&
232- d.bottomEdgeDemo.available &&
233- !demo.panel.indicators.partiallyOpened &&
234- !demo.panel.indicators.fullyOpened) {
235- d.bottomEdgeDemo.hideNow()
236- startLeftEdgeDemo()
237- }
238- }
239- }
240-
241- function startLeftEdgeDemo() {
242- demo.panelEnabled = false;
243- demo.launcherEnabled = true;
244- if (demo.stages) {
245- d.leftEdgeDemo = d.overlay.createObject(demo.stages, {
246- "edge": "left",
247- "title": i18n.tr("Left edge"),
248- "text": i18n.tr("Swipe from the left to reveal the launcher for quick access to apps"),
249- "anchors.fill": demo.stages,
250- });
251- }
252- if (d.leftEdgeDemo) {
253- d.leftEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
254- } else {
255- stopDemo();
256- }
257- }
258-
259- Connections {
260- target: demo.launcher
261- onStateChanged: {
262- if (d.leftEdgeDemo && d.leftEdgeDemo.available && launcher.state == "visible") {
263- d.leftEdgeDemo.hide()
264- launcher.hide()
265- startFinalEdgeDemo()
266- }
267- }
268- }
269-
270- function startFinalEdgeDemo() {
271- demo.launcherEnabled = false;
272- if (demo.stages) {
273- d.finalEdgeDemo = d.overlay.createObject(demo.stages, {
274- "edge": "none",
275- "title": i18n.tr("Well done"),
276- "text": i18n.tr("You have now mastered the edge gestures and can start using the phone<br><br>Tap on the screen to start"),
277- "anchors.fill": demo.stages,
278- "showSkip": false,
279- });
280- }
281- if (d.finalEdgeDemo) {
282- d.finalEdgeDemo.onSkip.connect(demo.hideEdgeDemoInShell)
283- } else {
284- stopDemo();
285- }
286- }
287-}
288
289=== removed file 'qml/Components/EdgeDemoOverlay.qml'
290--- qml/Components/EdgeDemoOverlay.qml 2014-07-01 23:45:28 +0000
291+++ qml/Components/EdgeDemoOverlay.qml 1970-01-01 00:00:00 +0000
292@@ -1,273 +0,0 @@
293-/*
294- * Copyright (C) 2013 Canonical, Ltd.
295- *
296- * This program is free software; you can redistribute it and/or modify
297- * it under the terms of the GNU General Public License as published by
298- * the Free Software Foundation; version 3.
299- *
300- * This program is distributed in the hope that it will be useful,
301- * but WITHOUT ANY WARRANTY; without even the implied warranty of
302- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303- * GNU General Public License for more details.
304- *
305- * You should have received a copy of the GNU General Public License
306- * along with this program. If not, see <http://www.gnu.org/licenses/>.
307- */
308-
309-import Powerd 0.1
310-import QtQuick 2.0
311-import QtGraphicalEffects 1.0
312-import Unity.Application 0.1
313-import Ubuntu.Components 0.1
314-
315-Showable {
316- id: overlay
317-
318- /*
319- * Valid values are "left", "right", "top", "bottom", or "none".
320- */
321- property string edge: "top"
322-
323- /*
324- * This is the header displayed, like "Right edge".
325- */
326- property alias title: titleLabel.text
327-
328- /*
329- * This is the block of text displayed below the header.
330- */
331- property alias text: textLabel.text
332-
333- /*
334- * This is the text for the skip button.
335- */
336- property alias skipText: skipLabel.text
337-
338- /*
339- * This is the visible status of the skip button.
340- */
341- property alias showSkip: skipLabel.visible
342-
343- /*
344- * Whether this demo is running currently.
345- */
346- readonly property bool active: available && visible
347-
348- /*
349- * Whether animations are paused.
350- */
351- property alias paused: wholeAnimation.paused
352-
353- /*
354- * Whether animations are running.
355- */
356- readonly property alias running: wholeAnimation.running
357-
358- signal skip()
359-
360- function doSkip() {
361- d.skipOnHide = true;
362- hide();
363- }
364-
365- function hideNow() {
366- overlay.visible = false;
367- overlay.available = false;
368- if (d.skipOnHide) {
369- overlay.skip();
370- }
371- }
372-
373- showAnimation: StandardAnimation {
374- property: "opacity"
375- to: 1
376- onRunningChanged: if (running) overlay.visible = true
377- }
378- hideAnimation: StandardAnimation {
379- property: "opacity"
380- to: 0
381- duration: UbuntuAnimation.BriskDuration
382- onRunningChanged: if (!running) overlay.hideNow()
383- }
384-
385- QtObject {
386- id: d
387- property bool skipOnHide: false
388- property int edgeMargin: units.gu(4)
389- }
390-
391- Rectangle {
392- objectName: "backgroundShade"
393-
394- anchors.fill: parent
395- color: "black"
396- opacity: 0.8
397- visible: overlay.active
398-
399- MouseArea {
400- objectName: "backgroundShadeMouseArea"
401-
402- anchors.fill: parent
403- enabled: overlay.edge == "none" && overlay.opacity == 1.0
404- onClicked: overlay.doSkip()
405- }
406- }
407-
408- Item {
409- id: hintGroup
410- x: 0
411- y: 0
412- width: parent.width
413- height: parent.height
414- visible: overlay.active
415-
416- Column {
417- id: labelGroup
418- spacing: units.gu(3)
419-
420- anchors {
421- margins: d.edgeMargin
422- left: parent.left
423- top: overlay.edge == "bottom" ? undefined : parent.top
424- bottom: overlay.edge == "bottom" ? parent.bottom : undefined
425- }
426-
427- Label {
428- id: titleLabel
429- fontSize: "x-large"
430- width: units.gu(25)
431- wrapMode: Text.WordWrap
432- }
433-
434- Label {
435- id: textLabel
436- width: units.gu(25)
437- wrapMode: Text.WordWrap
438- }
439-
440- Label {
441- id: skipLabel
442- objectName: "skipLabel"
443- text: i18n.tr("Skip intro")
444- color: UbuntuColors.orange
445- fontSize: "small"
446-
447- Icon {
448- anchors.left: parent.right
449- anchors.verticalCenter: parent.verticalCenter
450- height: units.dp(12)
451- width: units.dp(12)
452- name: "chevron"
453- color: UbuntuColors.orange
454- }
455-
456- MouseArea {
457- // Make clickable area bigger than just the link because
458- // otherwise, the edge demo will feel hard to dismiss.
459- anchors.fill: parent
460- anchors.margins: -units.gu(5)
461- onClicked: overlay.doSkip()
462- }
463- }
464- }
465-
466- LinearGradient {
467- id: edgeHint
468- property int size: 1
469- cached: false
470- visible: overlay.edge != "none"
471- gradient: Gradient {
472- GradientStop {
473- position: 0.0
474- color: Qt.hsla(16.0/360.0, 0.83, 0.47, 0.4) // UbuntuColors.orange, but transparent
475- }
476- GradientStop {
477- position: 1.0
478- color: "transparent"
479- }
480- }
481- anchors.fill: parent
482- start: {
483- if (overlay.edge == "right") {
484- return Qt.point(width, 0);
485- } else if (overlay.edge == "left") {
486- return Qt.point(0, 0);
487- } else if (overlay.edge == "top") {
488- return Qt.point(0, 0);
489- } else {
490- return Qt.point(0, height);
491- }
492- }
493- end: {
494- if (overlay.edge == "right") {
495- return Qt.point(width - size, 0);
496- } else if (overlay.edge == "left") {
497- return Qt.point(size, 0);
498- } else if (overlay.edge == "top") {
499- return Qt.point(0, size);
500- } else {
501- return Qt.point(0, height - size);
502- }
503- }
504- }
505- }
506-
507- SequentialAnimation {
508- id: wholeAnimation
509- objectName: "wholeAnimation"
510- running: overlay.active
511-
512- ParallelAnimation {
513- id: fadeInAnimation
514-
515- StandardAnimation {
516- target: labelGroup
517- property: {
518- if (overlay.edge == "right" || overlay.edge == "left") {
519- return "anchors.leftMargin";
520- } else if (overlay.edge == "bottom") {
521- return "anchors.bottomMargin";
522- } else {
523- return "anchors.topMargin";
524- }
525- }
526- from: {
527- if (overlay.edge == "right") {
528- return d.edgeMargin + units.gu(3)
529- } else {
530- return d.edgeMargin - units.gu(3)
531- }
532- }
533- to: d.edgeMargin
534- duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
535- }
536- StandardAnimation {
537- target: overlay
538- property: "opacity"
539- from: 0.0
540- to: 1.0
541- duration: UbuntuAnimation.SleepyDuration
542- }
543- }
544-
545- SequentialAnimation {
546- id: hintAnimation
547- loops: Animation.Infinite
548- property string prop: (overlay.edge == "left" || overlay.edge == "right") ? "x" : "y"
549- property double endVal: units.dp(5) * ((overlay.edge == "left" || overlay.edge == "top") ? 1 : -1)
550- property double maxGlow: units.dp(20)
551- property int duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
552-
553- ParallelAnimation {
554- StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: 0; to: hintAnimation.endVal; duration: hintAnimation.duration }
555- StandardAnimation { target: edgeHint; property: "size"; from: 1; to: hintAnimation.maxGlow; duration: hintAnimation.duration }
556- }
557-
558- // Undo the above
559- ParallelAnimation {
560- StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: hintAnimation.endVal; to: 0; duration: hintAnimation.duration }
561- StandardAnimation { target: edgeHint; property: "size"; from: hintAnimation.maxGlow; to: 1; duration: hintAnimation.duration }
562- }
563- }
564- }
565-}
566
567=== modified file 'qml/Launcher/Launcher.qml'
568--- qml/Launcher/Launcher.qml 2014-12-11 13:24:26 +0000
569+++ qml/Launcher/Launcher.qml 2015-02-11 17:00:33 +0000
570@@ -26,6 +26,7 @@
571 property bool autohideEnabled: false
572 property bool available: true // can be used to disable all interactions
573 property alias inverted: panel.inverted
574+ property bool shadeBackground: true // can be used to disable background shade when launcher is visible
575
576 property int panelWidth: units.gu(8)
577 property int dragAreaWidth: units.gu(1)
578@@ -33,6 +34,10 @@
579 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
580 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
581
582+ readonly property bool dragging: dragArea.dragging
583+ readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
584+ readonly property real visibleWidth: panel.width + panel.x
585+
586 readonly property bool shown: panel.x > -panel.width
587
588 // emitted when an application is selected
589@@ -154,7 +159,7 @@
590
591 MouseArea {
592 id: launcherDragArea
593- enabled: root.state == "visible"
594+ enabled: root.available && root.state == "visible"
595 anchors.fill: panel
596 anchors.rightMargin: -units.gu(2)
597 drag {
598@@ -180,7 +185,7 @@
599 right: parent.right
600 bottom: parent.bottom
601 }
602- enabled: root.state == "visible"
603+ enabled: root.shadeBackground && root.state == "visible"
604 onPressed: {
605 root.state = ""
606 }
607@@ -190,7 +195,7 @@
608 id: backgroundShade
609 anchors.fill: parent
610 color: "black"
611- opacity: root.state == "visible" ? 0.6 : 0
612+ opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
613
614 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
615 }
616@@ -198,14 +203,14 @@
617 LauncherPanel {
618 id: panel
619 objectName: "launcherPanel"
620- enabled: root.available
621+ enabled: root.available && root.state == "visible"
622 width: root.panelWidth
623 anchors {
624 top: parent.top
625 bottom: parent.bottom
626 }
627 x: -width
628- visible: x > -width || dragArea.status === DirectionalDragArea.Undecided
629+ visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
630 model: LauncherModel
631
632 property bool animate: true
633@@ -247,6 +252,7 @@
634 direction: Direction.Rightwards
635
636 enabled: root.available
637+ x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
638 width: root.dragAreaWidth
639 height: root.height
640
641@@ -260,7 +266,7 @@
642 // would appear right next to the user's finger out of nowhere.
643 // Instead, we make the panel go towards the user's finger in several
644 // steps. ie., in an animated way.
645- var targetPanelX = Math.min(0, touchX - panel.width)
646+ var targetPanelX = Math.min(0, touchX - panel.width) - root.x
647 var delta = targetPanelX - panel.x
648 // the trick is not to go all the way (1.0) as it would cause a sudden jump
649 panel.x += 0.4 * delta
650@@ -292,7 +298,7 @@
651 name: "visible"
652 PropertyChanges {
653 target: panel
654- x: 0
655+ x: -root.x // so we never go past panelWidth, even when teased by tutorial
656 }
657 },
658 State {
659
660=== modified file 'qml/Shell.qml'
661--- qml/Shell.qml 2015-02-11 17:00:33 +0000
662+++ qml/Shell.qml 2015-02-11 17:00:33 +0000
663@@ -36,6 +36,7 @@
664 import "Components"
665 import "Notifications"
666 import "Stages"
667+import "Tutorial"
668 import "Wizard"
669 import Unity.Notifications 1.0 as NotificationBackend
670 import Unity.Session 0.1
671@@ -62,7 +63,7 @@
672
673 readonly property bool locked: LightDM.Greeter.active && !LightDM.Greeter.authenticated && !forcedUnlock
674 readonly property alias hasLockedApp: greeter.hasLockedApp
675- readonly property bool forcedUnlock: edgeDemo.running
676+ readonly property bool forcedUnlock: tutorial.running
677 onForcedUnlockChanged: if (forcedUnlock) lockscreen.hide()
678
679 property bool sideStageEnabled: shell.width >= units.gu(100)
680@@ -161,7 +162,7 @@
681
682 ScreenGrabber {
683 id: screenGrabber
684- z: edgeDemo.z + 10
685+ z: dialogs.z + 10
686 enabled: Powerd.status === Powerd.On
687 }
688
689@@ -252,14 +253,16 @@
690 }
691
692 onApplicationAdded: {
693- if (greeter.shown && appId != "unity8-dash") {
694- greeter.startUnlock()
695+ if (appId != "unity8-dash") {
696+ if (greeter.shown) {
697+ greeter.startUnlock();
698+ }
699
700 // If this happens on first boot, we may be in edge
701 // tutorial or wizard while receiving a call. But a call
702 // is more important than wizard so just bail out of those.
703- if (edgeDemo.running) {
704- edgeDemo.hideEdgeDemos();
705+ if (tutorial.running) {
706+ tutorial.finish();
707 wizard.hide();
708 }
709 }
710@@ -287,7 +290,7 @@
711 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
712 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
713
714- property bool interactive: edgeDemo.stagesEnabled
715+ property bool interactive: tutorial.stagesEnabled
716 && !greeter.shown
717 && !lockscreen.shown
718 && panel.indicators.fullyClosed
719@@ -320,7 +323,7 @@
720 Binding {
721 target: applicationsDisplayLoader.item
722 property: "spreadEnabled"
723- value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
724+ value: tutorial.stagesEnabled && !greeter.hasLockedApp
725 }
726 Binding {
727 target: applicationsDisplayLoader.item
728@@ -653,9 +656,13 @@
729 LauncherModel.setUser(user);
730 }
731
732- onTapped: launcher.tease()
733+ onTapped: {
734+ if (!tutorial.running) {
735+ launcher.tease();
736+ }
737+ }
738 onDraggingChanged: {
739- if (dragging) {
740+ if (dragging && !tutorial.running) {
741 launcher.tease();
742 }
743 }
744@@ -693,7 +700,7 @@
745
746 onStatusChanged: {
747 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
748- !callManager.hasCalls && !edgeDemo.running) {
749+ !callManager.hasCalls && !tutorial.running) {
750 // We don't want to simply call greeter.showNow() here, because
751 // that will take too long. Qt will delay button event
752 // handling until the greeter is done loading and may think the
753@@ -709,7 +716,7 @@
754 }
755
756 function showHome() {
757- if (edgeDemo.running) {
758+ if (tutorial.running) {
759 return
760 }
761
762@@ -751,8 +758,8 @@
763 anchors.fill: parent //because this draws indicator menus
764 indicators {
765 hides: [launcher]
766- available: edgeDemo.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
767- contentEnabled: edgeDemo.panelContentEnabled
768+ available: tutorial.panelEnabled && (!shell.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
769+ contentEnabled: tutorial.panelContentEnabled
770 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
771
772 minimizedPanelHeight: units.gu(3)
773@@ -785,8 +792,9 @@
774 anchors.bottom: parent.bottom
775 width: parent.width
776 dragAreaWidth: shell.edgeSize
777- available: edgeDemo.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
778+ available: tutorial.launcherEnabled && (!shell.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
779 inverted: usageModeSettings.usageMode === "Staged"
780+ shadeBackground: !tutorial.running
781
782 onShowDashHome: showHome()
783 onDash: showDash()
784@@ -799,7 +807,7 @@
785 if (greeter.hasLockedApp) {
786 greeter.startUnlock()
787 }
788- if (!edgeDemo.running)
789+ if (!tutorial.running)
790 shell.activateApplication(appId)
791 }
792 onShownChanged: {
793@@ -872,15 +880,17 @@
794 }
795 }
796
797- EdgeDemo {
798- id: edgeDemo
799- objectName: "edgeDemo"
800- z: dialogs.z + 10
801- paused: Powerd.status === Powerd.Off || wizard.active // Saves power
802- greeter: greeter
803+ Tutorial {
804+ id: tutorial
805+ objectName: "tutorial"
806+ active: AccountsService.demoEdges
807+ paused: LightDM.Greeter.active
808 launcher: launcher
809 panel: panel
810 stages: stages
811+ overlay: overlay
812+
813+ onFinished: AccountsService.demoEdges = false
814 }
815
816 Connections {
817@@ -890,7 +900,7 @@
818
819 Rectangle {
820 id: shutdownFadeOutRectangle
821- z: edgeDemo.z + 10
822+ z: screenGrabber.z + 10
823 enabled: false
824 visible: false
825 color: "black"
826
827=== added directory 'qml/Tutorial'
828=== added file 'qml/Tutorial/Arrow.qml'
829--- qml/Tutorial/Arrow.qml 1970-01-01 00:00:00 +0000
830+++ qml/Tutorial/Arrow.qml 2015-02-11 17:00:33 +0000
831@@ -0,0 +1,56 @@
832+/*
833+ * Copyright (C) 2014 Canonical, Ltd.
834+ *
835+ * This program is free software; you can redistribute it and/or modify
836+ * it under the terms of the GNU General Public License as published by
837+ * the Free Software Foundation; version 3.
838+ *
839+ * This program is distributed in the hope that it will be useful,
840+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
841+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
842+ * GNU General Public License for more details.
843+ *
844+ * You should have received a copy of the GNU General Public License
845+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
846+ */
847+
848+import QtQuick 2.3
849+import Ubuntu.Components 1.1
850+
851+Item {
852+ id: root
853+
854+ property alias color: circle.color
855+
856+ // Will make whole arrow darker
857+ property real darkenBy: 0
858+
859+ property alias chevronOpacity: chevron.opacity
860+
861+ ////
862+
863+ Rectangle {
864+ id: circle
865+ anchors.fill: parent
866+ radius: width / 2
867+ }
868+
869+ Image {
870+ id: chevron
871+ anchors.centerIn: parent
872+ source: Qt.resolvedUrl("graphics/chevron.png")
873+ fillMode: Image.PreserveAspectFit
874+ sourceSize.width: 152
875+ sourceSize.height: 152
876+ width: parent.width / 2
877+ height: parent.height / 2
878+ }
879+
880+ Rectangle {
881+ id: darkCircle
882+ anchors.fill: parent
883+ radius: width / 2
884+ color: "black"
885+ opacity: root.darkenBy
886+ }
887+}
888
889=== added file 'qml/Tutorial/Slider.qml'
890--- qml/Tutorial/Slider.qml 1970-01-01 00:00:00 +0000
891+++ qml/Tutorial/Slider.qml 2015-02-11 17:00:33 +0000
892@@ -0,0 +1,117 @@
893+/*
894+ * Copyright (C) 2014 Canonical, Ltd.
895+ *
896+ * This program is free software; you can redistribute it and/or modify
897+ * it under the terms of the GNU General Public License as published by
898+ * the Free Software Foundation; version 3.
899+ *
900+ * This program is distributed in the hope that it will be useful,
901+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
902+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
903+ * GNU General Public License for more details.
904+ *
905+ * You should have received a copy of the GNU General Public License
906+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
907+ */
908+
909+import QtQuick 2.3
910+import Ubuntu.Components 1.1
911+
912+Item {
913+ id: root
914+
915+ // Whether this slider is short or long
916+ property bool shortSwipe
917+
918+ // How far the user has slid
919+ property real offset
920+
921+ // Set to true when slider is being used
922+ property bool active
923+
924+ // How far in percentage terms
925+ readonly property real percent: d.slideOffset / target.x
926+
927+ QtObject {
928+ id: d
929+ readonly property color trayColor: "#424141"
930+ readonly property real margin: units.gu(0.5)
931+ readonly property real arrowSize: root.height - margin * 2
932+ readonly property real dotSize: units.dp(1)
933+ readonly property real slideOffset: Math.min(root.offset - offscreenOffset, target.x)
934+ readonly property real offscreenOffset: units.gu(2)
935+ }
936+
937+ implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5)
938+ implicitHeight: units.gu(6.5)
939+
940+ Rectangle {
941+ color: d.trayColor
942+ anchors.fill: parent
943+ anchors.rightMargin: clipBox.width - 1
944+ }
945+
946+ // We want to have a circular border around the target. But we can't just
947+ // do a radius on two of a rectangle's corners. So we clip a full circle.
948+ Item {
949+ id: clipBox
950+
951+ clip: true
952+ anchors.top: parent.top
953+ anchors.bottom: parent.bottom
954+ anchors.right: parent.right
955+ width: parent.height / 2
956+
957+ Rectangle {
958+ color: d.trayColor
959+ anchors.top: parent.top
960+ anchors.bottom: parent.bottom
961+ anchors.right: parent.right
962+ width: parent.width * 2
963+ radius: parent.width
964+ }
965+ }
966+
967+ Arrow {
968+ id: target
969+ width: d.arrowSize
970+ height: d.arrowSize
971+ color: "#73000000"
972+ chevronOpacity: 0.52
973+ anchors.right: parent.right
974+ anchors.rightMargin: d.margin
975+ anchors.verticalCenter: parent.verticalCenter
976+ }
977+
978+ Row {
979+ anchors.left: handle.horizontalCenter
980+ anchors.right: target.horizontalCenter
981+ anchors.verticalCenter: parent.verticalCenter
982+
983+ layoutDirection: Qt.RightToLeft
984+ spacing: d.dotSize * 2
985+
986+ Repeater {
987+ model: parent.width / (parent.spacing + d.dotSize)
988+ Rectangle {
989+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
990+ height: d.dotSize
991+ width: height
992+ radius: width
993+ color: "white"
994+ opacity: 0.2
995+ }
996+ }
997+ }
998+
999+ Arrow {
1000+ id: handle
1001+ width: d.arrowSize
1002+ height: d.arrowSize
1003+ color: UbuntuColors.orange
1004+ darkenBy: root.active ? 0.5 : 0
1005+ anchors.left: parent.left
1006+ anchors.leftMargin: d.slideOffset
1007+ anchors.verticalCenter: parent.verticalCenter
1008+ }
1009+}
1010
1011=== added file 'qml/Tutorial/Tutorial.qml'
1012--- qml/Tutorial/Tutorial.qml 1970-01-01 00:00:00 +0000
1013+++ qml/Tutorial/Tutorial.qml 2015-02-11 17:00:33 +0000
1014@@ -0,0 +1,85 @@
1015+/*
1016+ * Copyright (C) 2014 Canonical, Ltd.
1017+ *
1018+ * This program is free software; you can redistribute it and/or modify
1019+ * it under the terms of the GNU General Public License as published by
1020+ * the Free Software Foundation; version 3.
1021+ *
1022+ * This program is distributed in the hope that it will be useful,
1023+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1024+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1025+ * GNU General Public License for more details.
1026+ *
1027+ * You should have received a copy of the GNU General Public License
1028+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1029+ */
1030+
1031+import QtQuick 2.3
1032+import Ubuntu.Components 1.1
1033+
1034+Item {
1035+ id: root
1036+
1037+ property alias active: loader.active
1038+ property bool paused
1039+
1040+ property Item launcher
1041+ property Item panel
1042+ property Item stages
1043+ property Item overlay
1044+
1045+ readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
1046+ readonly property bool stagesEnabled: loader.item ? loader.item.stagesEnabled : true
1047+ readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
1048+ readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
1049+ readonly property bool running: loader.item ? loader.item.running : false
1050+
1051+ function finish() {
1052+ if (loader.item) {
1053+ loader.item.finish();
1054+ }
1055+ }
1056+
1057+ signal finished()
1058+
1059+ Loader {
1060+ id: loader
1061+ anchors.fill: parent
1062+ source: "TutorialContent.qml"
1063+
1064+ Binding {
1065+ target: loader.item
1066+ property: "paused"
1067+ value: root.paused
1068+ }
1069+
1070+ Binding {
1071+ target: loader.item
1072+ property: "launcher"
1073+ value: root.launcher
1074+ }
1075+
1076+ Binding {
1077+ target: loader.item
1078+ property: "panel"
1079+ value: root.panel
1080+ }
1081+
1082+ Binding {
1083+ target: loader.item
1084+ property: "stages"
1085+ value: root.stages
1086+ }
1087+
1088+ Binding {
1089+ target: loader.item
1090+ property: "overlay"
1091+ value: root.overlay
1092+ }
1093+
1094+ Connections {
1095+ target: loader.item
1096+ onFinished: root.finished()
1097+ }
1098+ }
1099+}
1100
1101=== added file 'qml/Tutorial/TutorialContent.qml'
1102--- qml/Tutorial/TutorialContent.qml 1970-01-01 00:00:00 +0000
1103+++ qml/Tutorial/TutorialContent.qml 2015-02-11 17:00:33 +0000
1104@@ -0,0 +1,130 @@
1105+/*
1106+ * Copyright (C) 2013,2014 Canonical, Ltd.
1107+ *
1108+ * This program is free software; you can redistribute it and/or modify
1109+ * it under the terms of the GNU General Public License as published by
1110+ * the Free Software Foundation; version 3.
1111+ *
1112+ * This program is distributed in the hope that it will be useful,
1113+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1114+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1115+ * GNU General Public License for more details.
1116+ *
1117+ * You should have received a copy of the GNU General Public License
1118+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1119+ */
1120+
1121+import QtQuick 2.3
1122+import Ubuntu.Components 1.1
1123+
1124+Item {
1125+ id: root
1126+
1127+ property Item launcher
1128+ property Item panel
1129+ property Item stages
1130+ property Item overlay
1131+
1132+ readonly property bool launcherEnabled: !running ||
1133+ (!paused && loader.target === leftComponent)
1134+ readonly property bool stagesEnabled: !running
1135+ readonly property bool panelEnabled: !running
1136+ readonly property bool panelContentEnabled: !running
1137+ readonly property bool running: loader.sourceComponent !== null
1138+
1139+ property bool paused: false
1140+
1141+ signal finished()
1142+
1143+ function finish() {
1144+ d.stop();
1145+ finished();
1146+ }
1147+
1148+ ////
1149+
1150+ Component.onCompleted: {
1151+ d.start();
1152+ }
1153+
1154+ QtObject {
1155+ id: d
1156+
1157+ function stop() {
1158+ loader.sourceComponent = null;
1159+ }
1160+
1161+ function start() {
1162+ loader.load(leftComponent);
1163+ }
1164+ }
1165+
1166+ Loader {
1167+ id: loader
1168+ objectName: "tutorialLoader"
1169+
1170+ property Component target: {
1171+ if (next) {
1172+ return next;
1173+ } else if (loader.item && loader.item.shown) {
1174+ return sourceComponent;
1175+ } else {
1176+ return null;
1177+ }
1178+ }
1179+
1180+ property Component next: null
1181+
1182+ function load(comp) {
1183+ if (loader.item) {
1184+ next = comp;
1185+ loader.item.hide();
1186+ } else {
1187+ loader.sourceComponent = comp;
1188+ }
1189+ }
1190+
1191+ Connections {
1192+ target: loader.item
1193+ onFinished: {
1194+ loader.sourceComponent = loader.next;
1195+ if (loader.next != null) {
1196+ loader.next = null;
1197+ } else {
1198+ root.finished();
1199+ }
1200+ }
1201+ }
1202+
1203+ Binding {
1204+ target: loader.item
1205+ property: "paused"
1206+ value: root.paused
1207+ }
1208+ }
1209+
1210+ Component {
1211+ id: leftComponent
1212+ TutorialLeft {
1213+ objectName: "tutorialLeft"
1214+ parent: root.stages
1215+ anchors.fill: parent
1216+ launcher: root.launcher
1217+
1218+ onFinished: loader.load(leftFinishComponent)
1219+ }
1220+ }
1221+
1222+ Component {
1223+ id: leftFinishComponent
1224+ TutorialLeftFinish {
1225+ objectName: "tutorialLeftFinish"
1226+ parent: root.stages
1227+ anchors.fill: parent
1228+ textXOffset: root.launcher.panelWidth
1229+ backgroundFadesOut: true
1230+
1231+ onFinished: root.launcher.hide()
1232+ }
1233+ }
1234+}
1235
1236=== added file 'qml/Tutorial/TutorialLeft.qml'
1237--- qml/Tutorial/TutorialLeft.qml 1970-01-01 00:00:00 +0000
1238+++ qml/Tutorial/TutorialLeft.qml 2015-02-11 17:00:33 +0000
1239@@ -0,0 +1,91 @@
1240+/*
1241+ * Copyright (C) 2014 Canonical, Ltd.
1242+ *
1243+ * This program is free software; you can redistribute it and/or modify
1244+ * it under the terms of the GNU General Public License as published by
1245+ * the Free Software Foundation; version 3.
1246+ *
1247+ * This program is distributed in the hope that it will be useful,
1248+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1249+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1250+ * GNU General Public License for more details.
1251+ *
1252+ * You should have received a copy of the GNU General Public License
1253+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1254+ */
1255+
1256+import QtQuick 2.3
1257+import Ubuntu.Components 1.1
1258+import "." as LocalComponents
1259+
1260+TutorialPage {
1261+ id: root
1262+
1263+ property var launcher
1264+
1265+ title: i18n.tr("Open the launcher")
1266+ text: i18n.tr("Short swipe from the left edge.")
1267+
1268+ textXOffset: root.launcher.x + root.launcher.visibleWidth
1269+
1270+ Connections {
1271+ target: root.launcher
1272+
1273+ onStateChanged: {
1274+ if (root.launcher.state === "visible") {
1275+ finishTimer.start();
1276+ }
1277+ }
1278+
1279+ onDash: {
1280+ finishTimer.stop();
1281+ root.showError();
1282+ root.launcher.hide();
1283+ }
1284+ }
1285+
1286+ SequentialAnimation {
1287+ id: teaseAnimation
1288+ paused: running && root.paused
1289+ running: !slider.active && root.launcher.visibleWidth === 0 && root.shown
1290+ loops: Animation.Infinite
1291+
1292+ UbuntuNumberAnimation {
1293+ target: root.launcher
1294+ property: "x"
1295+ to: units.gu(2)
1296+ duration: UbuntuAnimation.SleepyDuration
1297+ }
1298+ UbuntuNumberAnimation {
1299+ target: root.launcher
1300+ property: "x"
1301+ to: 0
1302+ duration: UbuntuAnimation.SleepyDuration
1303+ }
1304+ }
1305+
1306+ Timer {
1307+ id: finishTimer
1308+ interval: 1
1309+ onTriggered: {
1310+ root.hide();
1311+ root.launcher.x = 0; // make sure to reset launcher before we go
1312+ }
1313+ }
1314+
1315+ foreground {
1316+ children: [
1317+ LocalComponents.Slider {
1318+ id: slider
1319+ anchors {
1320+ left: parent.left
1321+ top: parent.top
1322+ topMargin: root.textBottom + units.gu(3)
1323+ }
1324+ offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress
1325+ active: root.launcher.dragging
1326+ shortSwipe: true
1327+ }
1328+ ]
1329+ }
1330+}
1331
1332=== added file 'qml/Tutorial/TutorialLeftFinish.qml'
1333--- qml/Tutorial/TutorialLeftFinish.qml 1970-01-01 00:00:00 +0000
1334+++ qml/Tutorial/TutorialLeftFinish.qml 2015-02-11 17:00:33 +0000
1335@@ -0,0 +1,47 @@
1336+/*
1337+ * Copyright (C) 2014 Canonical, Ltd.
1338+ *
1339+ * This program is free software; you can redistribute it and/or modify
1340+ * it under the terms of the GNU General Public License as published by
1341+ * the Free Software Foundation; version 3.
1342+ *
1343+ * This program is distributed in the hope that it will be useful,
1344+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1345+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1346+ * GNU General Public License for more details.
1347+ *
1348+ * You should have received a copy of the GNU General Public License
1349+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1350+ */
1351+
1352+import QtQuick 2.3
1353+import Ubuntu.Components 1.1
1354+
1355+TutorialPage {
1356+ id: root
1357+
1358+ title: i18n.tr("These are the shortcuts to favorite apps")
1359+ text: i18n.tr("Tap here to finish.")
1360+ fullTextWidth: true
1361+
1362+ foreground {
1363+ children: [
1364+ Image {
1365+ objectName: "tick"
1366+ anchors {
1367+ horizontalCenter: parent.horizontalCenter
1368+ top: parent.top
1369+ topMargin: root.textBottom + units.gu(3)
1370+ }
1371+ source: Qt.resolvedUrl("graphics/tick.png")
1372+ height: units.gu(6.5)
1373+ width: units.gu(6.5)
1374+
1375+ MouseArea {
1376+ anchors.fill: parent
1377+ onClicked: root.hide()
1378+ }
1379+ }
1380+ ]
1381+ }
1382+}
1383
1384=== added file 'qml/Tutorial/TutorialPage.qml'
1385--- qml/Tutorial/TutorialPage.qml 1970-01-01 00:00:00 +0000
1386+++ qml/Tutorial/TutorialPage.qml 2015-02-11 17:00:33 +0000
1387@@ -0,0 +1,240 @@
1388+/*
1389+ * Copyright (C) 2013,2014 Canonical, Ltd.
1390+ *
1391+ * This program is free software; you can redistribute it and/or modify
1392+ * it under the terms of the GNU General Public License as published by
1393+ * the Free Software Foundation; version 3.
1394+ *
1395+ * This program is distributed in the hope that it will be useful,
1396+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1397+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1398+ * GNU General Public License for more details.
1399+ *
1400+ * You should have received a copy of the GNU General Public License
1401+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1402+ */
1403+
1404+import QtQuick 2.3
1405+import Ubuntu.Components 1.1
1406+import "../Components"
1407+
1408+Showable {
1409+ id: root
1410+
1411+ // This is the header displayed, like "Right edge"
1412+ property alias title: titleLabel.text
1413+
1414+ // This is the block of text displayed below the header
1415+ property alias text: textLabel.text
1416+
1417+ // Whether animations are paused
1418+ property bool paused
1419+
1420+ // Whether to give the text the full width that the title has
1421+ property bool fullTextWidth
1422+
1423+ // Whether whole page (background + foreground) or just the foreground fades in
1424+ property bool backgroundFadesIn: false
1425+
1426+ // Whether whole page (background + foreground) or just the foreground fades out
1427+ property bool backgroundFadesOut: false
1428+
1429+ // The foreground Item, add children to it that you want to fade in
1430+ property alias foreground: foregroundExtra
1431+
1432+ // The text label bottom, so you can position elements relative to it
1433+ readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height)
1434+
1435+ // The MouseArea that eats events (so you can adjust size as you will)
1436+ property alias mouseArea: mouseArea
1437+
1438+ // X/Y offsets for text
1439+ property real textXOffset: 0
1440+ property real textYOffset: 0
1441+
1442+ // Foreground opacity
1443+ property real foregroundOpacity: 1
1444+
1445+ signal finished()
1446+
1447+ function showError() {
1448+ errorTimer.start();
1449+ }
1450+
1451+ ////
1452+
1453+ shown: false
1454+ Component.onCompleted: show()
1455+
1456+ property real _foregroundHideOpacity
1457+
1458+ showAnimation: StandardAnimation {
1459+ property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity"
1460+ from: 0
1461+ to: 1
1462+ duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
1463+ }
1464+
1465+ hideAnimation: StandardAnimation {
1466+ property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
1467+ to: 0
1468+ duration: UbuntuAnimation.BriskDuration
1469+ onRunningChanged: {
1470+ if (!running) {
1471+ root.finished();
1472+ }
1473+ }
1474+ }
1475+
1476+ QtObject {
1477+ id: d
1478+
1479+ readonly property real sideMargin: units.gu(5.5)
1480+ readonly property real verticalOffset: -units.gu(9)
1481+ readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2))
1482+
1483+ property real fadeInOffset: {
1484+ if (showAnimation.running) {
1485+ var opacity = root[root.showAnimation.property]
1486+ return (1 - opacity) * units.gu(3);
1487+ } else {
1488+ return 0;
1489+ }
1490+ }
1491+ }
1492+
1493+ Timer {
1494+ id: errorTimer
1495+ interval: 3500
1496+ }
1497+
1498+ MouseArea { // eat any errant presses
1499+ id: mouseArea
1500+ anchors.fill: parent
1501+ }
1502+
1503+ Rectangle {
1504+ anchors.fill: parent
1505+ color: "black"
1506+ opacity: 0.82
1507+ }
1508+
1509+ Item {
1510+ id: foreground
1511+ anchors.fill: parent
1512+ opacity: root.foregroundOpacity < 1 ? root.foregroundOpacity : root._foregroundHideOpacity
1513+
1514+ Label {
1515+ id: titleLabel
1516+ anchors {
1517+ top: parent.verticalCenter
1518+ topMargin: d.verticalOffset + root.textYOffset
1519+ left: parent.left
1520+ leftMargin: d.sideMargin + d.textXOffset
1521+ }
1522+ width: parent.width - d.sideMargin * 2
1523+ horizontalAlignment: Text.AlignLeft
1524+ wrapMode: Text.Wrap
1525+ font.weight: Font.Light
1526+ font.pixelSize: units.gu(3.5)
1527+ }
1528+
1529+ Label {
1530+ id: textLabel
1531+ anchors {
1532+ top: titleLabel.bottom
1533+ topMargin: units.gu(2)
1534+ left: parent.left
1535+ leftMargin: d.sideMargin + d.textXOffset
1536+ }
1537+ width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
1538+ horizontalAlignment: Text.AlignLeft
1539+ wrapMode: Text.Wrap
1540+ font.weight: Font.Light
1541+ font.pixelSize: units.gu(2.5)
1542+ }
1543+
1544+ // We use two separate labels like this rather than just changing
1545+ // the text of the above labels because we want to know where to place
1546+ // sliders (via root.textBottom) without having that place change
1547+ // as the text changes length.
1548+ Label {
1549+ id: errorTitleLabel
1550+ objectName: "errorTitleLabel"
1551+ anchors {
1552+ top: titleLabel.top
1553+ left: titleLabel.left
1554+ }
1555+ width: titleLabel.width
1556+ horizontalAlignment: titleLabel.horizontalAlignment
1557+ wrapMode: titleLabel.wrapMode
1558+ font.weight: titleLabel.font.weight
1559+ font.pixelSize: titleLabel.font.pixelSize
1560+ opacity: 0
1561+ text: i18n.tr("You almost got it!")
1562+ }
1563+
1564+ Label {
1565+ id: errorTextLabel
1566+ objectName: "errorTextLabel"
1567+ anchors {
1568+ top: errorTitleLabel.bottom
1569+ topMargin: textLabel.anchors.topMargin
1570+ left: textLabel.left
1571+ }
1572+ width: textLabel.width
1573+ horizontalAlignment: textLabel.horizontalAlignment
1574+ wrapMode: textLabel.wrapMode
1575+ font.weight: textLabel.font.weight
1576+ font.pixelSize: textLabel.font.pixelSize
1577+ opacity: 0
1578+ text: i18n.tr("Try again.")
1579+ }
1580+
1581+ // A place for subclasses to add extra widgets
1582+ Item {
1583+ id: foregroundExtra
1584+ anchors.fill: parent
1585+ }
1586+ }
1587+
1588+ states: State {
1589+ name: "errorState"
1590+ when: errorTimer.running
1591+ PropertyChanges { target: titleLabel; opacity: 0 }
1592+ PropertyChanges { target: textLabel; opacity: 0 }
1593+ PropertyChanges { target: errorTitleLabel; opacity: 1 }
1594+ PropertyChanges { target: errorTextLabel; opacity: 1 }
1595+ }
1596+
1597+ transitions: Transition {
1598+ to: "errorState"
1599+ reversible: true
1600+ SequentialAnimation {
1601+ ParallelAnimation {
1602+ StandardAnimation {
1603+ target: titleLabel
1604+ property: "opacity"
1605+ duration: UbuntuAnimation.BriskDuration
1606+ }
1607+ StandardAnimation {
1608+ target: textLabel
1609+ property: "opacity"
1610+ duration: UbuntuAnimation.BriskDuration
1611+ }
1612+ }
1613+ ParallelAnimation {
1614+ StandardAnimation {
1615+ target: errorTitleLabel
1616+ property: "opacity"
1617+ duration: UbuntuAnimation.BriskDuration
1618+ }
1619+ StandardAnimation {
1620+ target: errorTextLabel
1621+ property: "opacity"
1622+ duration: UbuntuAnimation.BriskDuration
1623+ }
1624+ }
1625+ }
1626+ }
1627+}
1628
1629=== added directory 'qml/Tutorial/graphics'
1630=== added file 'qml/Tutorial/graphics/chevron.png'
1631Binary files qml/Tutorial/graphics/chevron.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/chevron.png 2015-02-11 17:00:33 +0000 differ
1632=== added file 'qml/Tutorial/graphics/tick.png'
1633Binary files qml/Tutorial/graphics/tick.png 1970-01-01 00:00:00 +0000 and qml/Tutorial/graphics/tick.png 2015-02-11 17:00:33 +0000 differ
1634=== renamed file 'tests/autopilot/unity8/shell/emulators/edges_demo.py' => 'tests/autopilot/unity8/shell/emulators/tutorial.py'
1635--- tests/autopilot/unity8/shell/emulators/edges_demo.py 2014-12-16 16:32:40 +0000
1636+++ tests/autopilot/unity8/shell/emulators/tutorial.py 2015-02-11 17:00:33 +0000
1637@@ -29,112 +29,26 @@
1638 logger = logging.getLogger(__name__)
1639
1640
1641-class RightEdgeDemoOverlay(
1642- ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1643-
1644- @classmethod
1645- def validate_dbus_object(cls, path, state):
1646- name = introspection.get_classname_from_path(path)
1647- if name == b'EdgeDemoOverlay':
1648- if state['edge'][1] == 'right':
1649- return True
1650- return False
1651-
1652- @autopilot.logging.log_action(logger.info)
1653- def swipe(self):
1654- """Swipe to the left to complete this demo step."""
1655- x, y, width, height = self.globalRect
1656- start_x = x + width
1657- stop_x = x
1658- start_y = stop_y = y + height // 2
1659- self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1660- return self.get_root_instance().wait_select_single(
1661- edge='top', active=True)
1662-
1663-
1664-class TopEdgeDemoOverlay(
1665- ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1666-
1667- @classmethod
1668- def validate_dbus_object(cls, path, state):
1669- name = introspection.get_classname_from_path(path)
1670- if name == b'EdgeDemoOverlay':
1671- if state['edge'][1] == 'top':
1672- return True
1673- return False
1674-
1675- @autopilot.logging.log_action(logger.info)
1676- def swipe(self):
1677- """Swipe to the bottom to complete this demo step."""
1678- x, y, width, height = self.globalRect
1679- start_x = stop_x = x + width // 2
1680- start_y = y
1681- stop_y = y + height
1682- self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1683- return self.get_root_instance().wait_select_single(
1684- edge='bottom', active=True)
1685-
1686-
1687-class BottomEdgeDemoOverlay(
1688- ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1689-
1690- @classmethod
1691- def validate_dbus_object(cls, path, state):
1692- name = introspection.get_classname_from_path(path)
1693- if name == b'EdgeDemoOverlay':
1694- if state['edge'][1] == 'bottom':
1695- return True
1696- return False
1697-
1698- @autopilot.logging.log_action(logger.info)
1699- def swipe(self):
1700- """Swipe to the top to complete this demo step."""
1701- x, y, width, height = self.globalRect
1702- start_x = stop_x = x + width // 2
1703- start_y = y + height
1704- stop_y = y
1705- self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1706- return self.get_root_instance().wait_select_single(
1707- edge='left', active=True)
1708-
1709-
1710-class LeftEdgeDemoOverlay(
1711- ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1712-
1713- @classmethod
1714- def validate_dbus_object(cls, path, state):
1715- name = introspection.get_classname_from_path(path)
1716- if name == b'EdgeDemoOverlay':
1717- if state['edge'][1] == 'left':
1718- return True
1719- return False
1720-
1721- @autopilot.logging.log_action(logger.info)
1722- def swipe(self):
1723- """Swipe to the right to complete this demo step."""
1724+class TutorialPage(
1725+ ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1726+
1727+ @classmethod
1728+ def validate_dbus_object(cls, path, state):
1729+ name = introspection.get_classname_from_path(path)
1730+ return name == b'TutorialPage' or name == b'TutorialLeft'
1731+
1732+ @autopilot.logging.log_action(logger.info)
1733+ def short_swipe_right(self):
1734 x, y, width, height = self.globalRect
1735 start_x = x
1736- stop_x = x + width
1737+ stop_x = x + width // 3
1738 start_y = stop_y = y + height // 2
1739 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
1740- return self.get_root_instance().wait_select_single(
1741- edge='none', active=True)
1742-
1743-
1744-class FinalEdgeDemoOverlay(
1745- ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
1746-
1747- @classmethod
1748- def validate_dbus_object(cls, path, state):
1749- name = introspection.get_classname_from_path(path)
1750- if name == b'EdgeDemoOverlay':
1751- if state['edge'][1] == 'none':
1752- return True
1753- return False
1754+ self.shown.wait_for(False)
1755
1756 @autopilot.logging.log_action(logger.info)
1757- def tap_to_start(self):
1758- """Tap to finish the demo and start using the Ubuntu Touch."""
1759- time.sleep(1)
1760- self.pointing_device.click_object(self)
1761+ def tap(self):
1762+ """Tap the tick button to complete this step."""
1763+ button = self.select_single(objectName="tick")
1764+ self.pointing_device.click_object(button)
1765 self.shown.wait_for(False)
1766
1767=== modified file 'tests/autopilot/unity8/shell/fixture_setup.py'
1768--- tests/autopilot/unity8/shell/fixture_setup.py 2014-12-16 16:32:40 +0000
1769+++ tests/autopilot/unity8/shell/fixture_setup.py 2015-02-11 17:00:33 +0000
1770@@ -54,7 +54,7 @@
1771 return ld_library_path
1772
1773
1774-class EdgesDemo(fixtures.Fixture):
1775+class Tutorial(fixtures.Fixture):
1776
1777 def __init__(self, enable):
1778 super().__init__()
1779@@ -62,12 +62,12 @@
1780
1781 def setUp(self):
1782 super().setUp()
1783- original_state = self._is_edges_demo_enabled()
1784+ original_state = self._is_tutorial_enabled()
1785 if self.enable != original_state:
1786- self.addCleanup(self._set_edges_demo, original_state)
1787- self._set_edges_demo(self.enable)
1788+ self.addCleanup(self._set_tutorial, original_state)
1789+ self._set_tutorial(self.enable)
1790
1791- def _is_edges_demo_enabled(self):
1792+ def _is_tutorial_enabled(self):
1793 command = [
1794 'dbus-send', '--system', '--print-reply',
1795 '--dest=org.freedesktop.Accounts',
1796@@ -79,7 +79,7 @@
1797 output = subprocess.check_output(command, universal_newlines=True)
1798 return True if output.count('true') else False
1799
1800- def _set_edges_demo(self, value):
1801+ def _set_tutorial(self, value):
1802 value_string = 'true' if value else 'false'
1803 command = [
1804 'dbus-send', '--system', '--print-reply',
1805
1806=== renamed file 'tests/autopilot/unity8/shell/tests/test_edges_demo.py' => 'tests/autopilot/unity8/shell/tests/test_tutorial.py'
1807--- tests/autopilot/unity8/shell/tests/test_edges_demo.py 2015-01-21 13:50:14 +0000
1808+++ tests/autopilot/unity8/shell/tests/test_tutorial.py 2015-02-11 17:00:33 +0000
1809@@ -24,28 +24,27 @@
1810 fixture_setup,
1811 tests
1812 )
1813-# unused import to load the edge emulators custom proxy objects.
1814-from unity8.shell.emulators import edges_demo # NOQA
1815-
1816-
1817-class EdgesDemoTestCase(tests.UnityTestCase):
1818+# unused import to load the tutorial emulators custom proxy objects.
1819+from unity8.shell.emulators import tutorial # NOQA
1820+
1821+
1822+class TutorialTestCase(tests.UnityTestCase):
1823
1824 def setUp(self):
1825- super(EdgesDemoTestCase, self).setUp()
1826+ super(TutorialTestCase, self).setUp()
1827 self._qml_mock_enabled = False
1828 self._data_dirs_mock_enabled = False
1829
1830- self.useFixture(fixture_setup.EdgesDemo(True))
1831+ self.useFixture(fixture_setup.Tutorial(True))
1832 self.unity = self.launch_unity()
1833
1834- def test_complete_edge_demo(self):
1835- edge_demo = self.unity.select_single('EdgeDemo')
1836- self.assertThat(edge_demo.running, Eventually(Equals(True)))
1837- right_edge_overlay = self.unity.wait_select_single(
1838- edge='right', active=True)
1839- top_edge_overlay = right_edge_overlay.swipe()
1840- bottom_edge_overlay = top_edge_overlay.swipe()
1841- left_edge_overlay = bottom_edge_overlay.swipe()
1842- final_overlay = left_edge_overlay.swipe()
1843- final_overlay.tap_to_start()
1844- self.assertThat(edge_demo.running, Eventually(Equals(False)))
1845+ def test_complete_tutorial(self):
1846+ greeter = self.main_window.get_greeter()
1847+ tutorial = self.unity.select_single('Tutorial')
1848+ self.assertThat(tutorial.running, Eventually(Equals(True)))
1849+ greeter.swipe()
1850+ page = self.unity.wait_select_single(objectName='tutorialLeft')
1851+ page.short_swipe_right()
1852+ page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
1853+ page.tap()
1854+ self.assertThat(tutorial.running, Eventually(Equals(False)))
1855
1856=== modified file 'tests/qmltests/CMakeLists.txt'
1857--- tests/qmltests/CMakeLists.txt 2015-02-11 17:00:33 +0000
1858+++ tests/qmltests/CMakeLists.txt 2015-02-11 17:00:33 +0000
1859@@ -25,7 +25,6 @@
1860 add_qml_test(Components Carousel)
1861 add_qml_test(Components Dialogs)
1862 add_qml_test(Components DraggingArea)
1863-add_qml_test(Components EdgeDemoOverlay)
1864 add_qml_test(Components LazyImage)
1865 add_qml_test(Components Lockscreen)
1866 add_qml_test(Components Rating)
1867@@ -91,4 +90,5 @@
1868 add_qml_test(Stages TabletStage)
1869 add_qml_test(Stages WindowMoveResizeArea)
1870 add_qml_test(Stages Splash)
1871+add_qml_test(Tutorial Tutorial)
1872 add_qml_test(Wizard Wizard)
1873
1874=== removed file 'tests/qmltests/Components/tst_EdgeDemoOverlay.qml'
1875--- tests/qmltests/Components/tst_EdgeDemoOverlay.qml 2013-12-17 16:04:47 +0000
1876+++ tests/qmltests/Components/tst_EdgeDemoOverlay.qml 1970-01-01 00:00:00 +0000
1877@@ -1,123 +0,0 @@
1878-/*
1879- * Copyright 2013 Canonical Ltd.
1880- *
1881- * This program is free software; you can redistribute it and/or modify
1882- * it under the terms of the GNU General Public License as published by
1883- * the Free Software Foundation; version 3.
1884- *
1885- * This program is distributed in the hope that it will be useful,
1886- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1887- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1888- * GNU General Public License for more details.
1889- *
1890- * You should have received a copy of the GNU General Public License
1891- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1892- */
1893-
1894-import QtQuick 2.0
1895-import QtTest 1.0
1896-import Unity.Test 0.1 as UT
1897-import "../../../qml/Components"
1898-
1899-Item {
1900- id: root
1901- width: boxWidth * 3
1902- height: boxHeight * 2
1903-
1904- property int boxWidth: 250
1905- property int boxHeight: 250
1906-
1907- EdgeDemoOverlay {
1908- id: top
1909- edge: "top"
1910- title: "Top"
1911- text: "Displayed on top left"
1912- anchors.left: parent.left
1913- anchors.top: parent.top
1914- width: boxWidth
1915- height: boxHeight
1916- }
1917-
1918- EdgeDemoOverlay {
1919- id: right
1920- edge: "right"
1921- title: "Right"
1922- text: "Displayed on top right"
1923- anchors.right: parent.right
1924- anchors.top: parent.top
1925- width: boxWidth
1926- height: boxHeight
1927- }
1928-
1929- EdgeDemoOverlay {
1930- id: left
1931- edge: "left"
1932- title: "Left"
1933- text: "Displayed on bottom right"
1934- anchors.right: parent.right
1935- anchors.bottom: parent.bottom
1936- width: boxWidth
1937- height: boxHeight
1938- available: false
1939- }
1940-
1941- EdgeDemoOverlay {
1942- id: bottom
1943- edge: "bottom"
1944- title: "Bottom"
1945- text: "Displayed on bottom left"
1946- anchors.left: parent.left
1947- anchors.bottom: parent.bottom
1948- width: boxWidth
1949- height: boxHeight
1950- }
1951-
1952- EdgeDemoOverlay {
1953- id: none
1954- edge: "none"
1955- title: "None"
1956- text: "Displayed on top middle"
1957- anchors.horizontalCenter: parent.horizontalCenter
1958- anchors.top: parent.top
1959- width: boxWidth
1960- height: boxHeight
1961- }
1962-
1963- SignalSpy {
1964- id: signalSpy
1965- }
1966-
1967- UT.UnityTestCase {
1968- name: "EdgeDemoOverlay"
1969- when: windowShown
1970-
1971- function test_animations() {
1972- compare(right.running, true)
1973-
1974- compare(left.running, false)
1975- left.available = true
1976- compare(left.running, true)
1977- }
1978-
1979- function test_skip() {
1980- signalSpy.target = bottom
1981- signalSpy.signalName = "skip"
1982- signalSpy.clear()
1983- var bottomSkip = findChild(bottom, "skipLabel")
1984- mousePress(bottomSkip, 1, 1)
1985- mouseRelease(bottomSkip, 1, 1)
1986- signalSpy.wait()
1987- compare(bottom.available, false)
1988-
1989- // Test that the 'none' edge skips anywhere
1990- signalSpy.target = none
1991- signalSpy.clear()
1992- var backgroundShade = findChild(none, "backgroundShadeMouseArea")
1993- tryCompare(backgroundShade, "enabled", true)
1994- mousePress(backgroundShade, 1, 1)
1995- mouseRelease(backgroundShade, 1, 1)
1996- signalSpy.wait()
1997- compare(none.available, false)
1998- }
1999- }
2000-}
2001
2002=== added directory 'tests/qmltests/Tutorial'
2003=== added file 'tests/qmltests/Tutorial/tst_Tutorial.qml'
2004--- tests/qmltests/Tutorial/tst_Tutorial.qml 1970-01-01 00:00:00 +0000
2005+++ tests/qmltests/Tutorial/tst_Tutorial.qml 2015-02-11 17:00:33 +0000
2006@@ -0,0 +1,286 @@
2007+/*
2008+ * Copyright (C) 2013,2014 Canonical, Ltd.
2009+ *
2010+ * This program is free software; you can redistribute it and/or modify
2011+ * it under the terms of the GNU General Public License as published by
2012+ * the Free Software Foundation; version 3.
2013+ *
2014+ * This program is distributed in the hope that it will be useful,
2015+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2016+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2017+ * GNU General Public License for more details.
2018+ *
2019+ * You should have received a copy of the GNU General Public License
2020+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2021+ */
2022+
2023+import QtQuick 2.0
2024+import QtTest 1.0
2025+import AccountsService 0.1
2026+import LightDM 0.1 as LightDM
2027+import Ubuntu.Components 1.1
2028+import Unity.Application 0.1
2029+import Unity.Test 0.1 as UT
2030+
2031+import "../../../qml"
2032+
2033+Item {
2034+ id: root
2035+ width: shellLoader.width + buttons.width
2036+ height: shellLoader.height
2037+
2038+ QtObject {
2039+ id: applicationArguments
2040+
2041+ function hasGeometry() {
2042+ return false;
2043+ }
2044+
2045+ function width() {
2046+ return 0;
2047+ }
2048+
2049+ function height() {
2050+ return 0;
2051+ }
2052+ }
2053+
2054+ Component.onCompleted: {
2055+ // must set the mock mode before loading the Shell
2056+ LightDM.Greeter.mockMode = "single-pin";
2057+ LightDM.Users.mockMode = "single-pin";
2058+ shellLoader.active = true;
2059+ }
2060+
2061+ Row {
2062+ spacing: 0
2063+ anchors.fill: parent
2064+
2065+ Loader {
2066+ id: shellLoader
2067+
2068+ active: false
2069+ width: units.gu(40)
2070+ height: units.gu(71)
2071+
2072+ property bool itemDestroyed: false
2073+ sourceComponent: Component {
2074+ Shell {
2075+ property string indicatorProfile: "phone"
2076+
2077+ Component.onDestruction: {
2078+ shellLoader.itemDestroyed = true;
2079+ }
2080+ }
2081+ }
2082+ }
2083+
2084+ Rectangle {
2085+ id: buttons
2086+ color: "white"
2087+ width: units.gu(30)
2088+ height: shellLoader.height
2089+
2090+ Column {
2091+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
2092+ spacing: units.gu(1)
2093+ Row {
2094+ anchors { left: parent.left; right: parent.right }
2095+ Button {
2096+ text: "Restart Tutorial"
2097+ onClicked: {
2098+ if (shellLoader.status !== Loader.Ready)
2099+ return;
2100+
2101+ AccountsService.demoEdges = false;
2102+ AccountsService.demoEdges = true;
2103+ }
2104+ }
2105+ }
2106+ }
2107+ }
2108+ }
2109+
2110+ UT.UnityTestCase {
2111+ id: testCase
2112+ name: "Tutorial"
2113+ when: windowShown
2114+
2115+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
2116+ property real halfWidth: shell ? shell.width / 2 : 0
2117+ property real halfHeight: shell ? shell.height / 2 : 0
2118+
2119+ function init() {
2120+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
2121+ swipeAwayGreeter();
2122+ AccountsService.demoEdges = false;
2123+ AccountsService.demoEdges = true;
2124+ }
2125+
2126+ function cleanup() {
2127+ shellLoader.itemDestroyed = false;
2128+
2129+ shellLoader.active = false;
2130+
2131+ tryCompare(shellLoader, "status", Loader.Null);
2132+ tryCompare(shellLoader, "item", null);
2133+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
2134+ // item might still be alive. So if we set Loader.active back to true
2135+ // again right now we will get the very same Shell instance back. So no reload
2136+ // actually took place. Likely because Loader waits until the next event loop
2137+ // iteration to do its work. So to ensure the reload, we will wait until the
2138+ // Shell instance gets destroyed.
2139+ tryCompare(shellLoader, "itemDestroyed", true);
2140+
2141+ // kill all (fake) running apps
2142+ killApps();
2143+
2144+ // reload our test subject to get it in a fresh state once again
2145+ shellLoader.active = true;
2146+
2147+ tryCompare(shellLoader, "status", Loader.Ready);
2148+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
2149+ }
2150+
2151+ function killApps() {
2152+ while (ApplicationManager.count > 1) {
2153+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
2154+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
2155+ }
2156+ compare(ApplicationManager.count, 1)
2157+ }
2158+
2159+ function swipeAwayGreeter() {
2160+ var greeter = findChild(shell, "greeter");
2161+ tryCompare(greeter, "showProgress", 1);
2162+
2163+ touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
2164+
2165+ // wait until the animation has finished
2166+ tryCompare(greeter, "showProgress", 0);
2167+ waitForRendering(greeter);
2168+ }
2169+
2170+ function waitForPage(name) {
2171+ tryCompareFunction(function() { return findChild(shell, name) !== null; }, true);
2172+ waitForRendering(findChild(shell, name));
2173+ var page = findChild(shell, name);
2174+ tryCompare(page, "shown", true);
2175+ tryCompare(page.showAnimation, "running", false);
2176+ return page;
2177+ }
2178+
2179+ function checkTopEdge() {
2180+ touchFlick(shell, halfWidth, 0, halfWidth, halfHeight);
2181+
2182+ var panel = findChild(shell, "panel");
2183+ tryCompare(panel.indicators, "fullyClosed", true);
2184+ }
2185+
2186+ function checkLeftEdge() {
2187+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
2188+
2189+ var launcher = findChild(shell, "launcher");
2190+ tryCompare(launcher, "state", "");
2191+ }
2192+
2193+ function checkRightEdge() {
2194+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
2195+
2196+ var stage = findChild(shell, "stage");
2197+ var spreadView = findChild(stage, "spreadView");
2198+ tryCompare(spreadView, "phase", 0);
2199+ }
2200+
2201+ function checkBottomEdge() {
2202+ // Can't actually check effect of swipe, since dash isn't really loaded
2203+ var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
2204+ tryCompare(applicationsDisplayLoader.item, "interactive", false);
2205+ }
2206+
2207+ function checkFinished() {
2208+ tryCompare(AccountsService, "demoEdges", false);
2209+
2210+ var tutorial = findChild(shell, "tutorial");
2211+ tryCompare(tutorial, "running", false);
2212+
2213+ var launcher = findChild(shell, "launcher");
2214+ tryCompare(launcher, "shown", false);
2215+ }
2216+
2217+ function goToPage(name) {
2218+ var page = waitForPage("tutorialLeft");
2219+ checkTopEdge();
2220+ checkRightEdge();
2221+ checkBottomEdge();
2222+ if (name === "tutorialLeft") return page;
2223+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
2224+
2225+ page = waitForPage("tutorialLeftFinish");
2226+ if (name === "tutorialLeftFinish") return page;
2227+ var tick = findChild(page, "tick");
2228+ tap(tick);
2229+
2230+ checkFinished();
2231+ return null;
2232+ }
2233+
2234+ function test_walkthrough() {
2235+ goToPage(null);
2236+ }
2237+
2238+ function test_launcherShortDrag() {
2239+ // goToPage does a normal launcher pull. But here we want to test
2240+ // just barely pulling the launcher out and letting go (i.e. not
2241+ // triggering the "progress" property of Launcher).
2242+
2243+ var left = goToPage("tutorialLeft");
2244+
2245+ // Make sure we don't do anything if we don't pull the launcher
2246+ // out much.
2247+ var launcher = findChild(shell, "launcher");
2248+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
2249+ tryCompare(launcher, "state", ""); // should remain hidden
2250+ tryCompare(left, "shown", true); // and we should still be on left
2251+
2252+ // Now drag out but not past launcher itself
2253+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
2254+
2255+ waitForPage("tutorialLeftFinish");
2256+ }
2257+
2258+ function test_launcherLongDrag() {
2259+ // goToPage does a normal launcher pull. But here we want to test
2260+ // a full pull across the page.
2261+
2262+ var left = goToPage("tutorialLeft");
2263+
2264+ var launcher = findChild(shell, "launcher");
2265+ touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
2266+
2267+ var errorTextLabel = findChild(left, "errorTextLabel");
2268+ var errorTitleLabel = findChild(left, "errorTitleLabel");
2269+ tryCompare(launcher, "state", ""); // launcher goes away
2270+ tryCompare(left, "shown", true); // still on left page
2271+ tryCompare(errorTextLabel, "opacity", 1); // show error
2272+ tryCompare(errorTitleLabel, "opacity", 1); // show error
2273+ }
2274+
2275+ function test_launcherDragBack() {
2276+ // goToPage does a full launcher pull. But here we test pulling
2277+ // all the way out, then dragging back into place.
2278+
2279+ var left = goToPage("tutorialLeft");
2280+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false);
2281+ touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true);
2282+
2283+ tryCompare(left, "shown", true); // and we should still be on left
2284+ }
2285+
2286+ function test_interrupted() {
2287+ goToPage("tutorialLeft");
2288+ ApplicationManager.startApplication("dialer-app");
2289+ checkFinished();
2290+ }
2291+ }
2292+}
2293
2294=== modified file 'tests/qmltests/tst_TabletShell.qml'
2295--- tests/qmltests/tst_TabletShell.qml 2015-01-20 11:50:19 +0000
2296+++ tests/qmltests/tst_TabletShell.qml 2015-02-11 17:00:33 +0000
2297@@ -303,8 +303,8 @@
2298 selectUser(data.user)
2299
2300 AccountsService.demoEdges = data.demo
2301- var edgeDemo = findChild(shell, "edgeDemo")
2302- tryCompare(edgeDemo, "running", data.demo)
2303+ var tutorial = findChild(shell, "tutorial");
2304+ tryCompare(tutorial, "running", data.demo);
2305
2306 swipeFromLeftEdge(shell.width * 0.75)
2307 wait(500) // to give time to handle dash() signal from Launcher

Subscribers

People subscribed via source and target branches