on xenial help sign in
Merge from tutorial-new-screens
commit7dfc54995765e7f9ec96029ca0daf250ac161099 authorMichael Terry <michael.terry@canonical.com> dateThu 5 Feb 2015, 11:33 (12 years ago) parent849c0861b9f525
34 files changed+1767−646
debian/unity8.install+1 qml/CMakeLists.txt+3−2 qml/Components/EdgeDemo.qml → qml/Components/EdgeDemo.qml.THIS qml/Components/EdgeDemoOverlay.qml−273 qml/Launcher/Launcher.qml+13−7 qml/Shell.qml+32−20 qml/Stages/ApplicationWindow.qml+14−1 qml/Stages/PhoneStage.qml+62−34 qml/Stages/TransformedSpreadDelegate.qml+27−21 qml/Tutorial/Arrow.qml+56 qml/Tutorial/Slider.qml+123 qml/Tutorial/Tick.qml+29 qml/Tutorial/Tutorial.qml+92 qml/Tutorial/TutorialBottom.qml+104 qml/Tutorial/TutorialBottomFinish.qml+41 qml/Tutorial/TutorialContent.qml+124 qml/Tutorial/TutorialLeft.qml+91 qml/Tutorial/TutorialLeftFinish.qml+41 qml/Tutorial/TutorialPage.qml+245 qml/Tutorial/TutorialRight.qml+223 qml/Tutorial/graphics/camera.png qml/Tutorial/graphics/chevron.png qml/Tutorial/graphics/dialer.png qml/Tutorial/graphics/facebook.png qml/Tutorial/graphics/gallery.png qml/Tutorial/graphics/tick.png tests/autopilot/unity8/shell/emulators/edges_demo.py−140 tests/autopilot/unity8/shell/emulators/tutorial.py+74 tests/autopilot/unity8/shell/fixture_setup.py+6−6 tests/autopilot/unity8/shell/tests/test_edges_demo.py → tests/autopilot/unity8/shell/tests/test_tutorial.py+22−16 tests/qmltests/CMakeLists.txt+1−1 tests/qmltests/Components/tst_EdgeDemoOverlay.qml−123 tests/qmltests/Tutorial/tst_Tutorial.qml+341 tests/qmltests/tst_TabletShell.qml+2−2
debian/unity8.install+1
@@ -10,5 +10,6 @@ usr/share/unity8/Launcher
1010usr/share/unity8/Panel
1111usr/share/unity8/Shell.qml
1212usr/share/unity8/Stages
13+usr/share/unity8/Tutorial
1314usr/share/unity8/Wizard
1415usr/share/url-dispatcher/urls/unity8-dash.url-dispatcher
qml/CMakeLists.txt+3−2
@@ -9,10 +9,11 @@ set(QML_DIRS
99 Dash
1010 graphics
1111 Greeter
12− Panel
1312 Launcher
14− Stages
1513 Notifications
14+ Panel
15+ Stages
16+ Tutorial
1617 Wizard
1718 )
1819
qml/Components/EdgeDemo.qml → qml/Components/EdgeDemo.qml.THISrenamed

The content is the same.

qml/Components/EdgeDemoOverlay.qmldeleted−273
@@ -1,273 +0,0 @@
1−/*
2− * Copyright (C) 2013 Canonical, Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import Powerd 0.1
18−import QtQuick 2.0
19−import QtGraphicalEffects 1.0
20−import Unity.Application 0.1
21−import Ubuntu.Components 0.1
22−
23−Showable {
24− id: overlay
25−
26− /*
27− * Valid values are "left", "right", "top", "bottom", or "none".
28− */
29− property string edge: "top"
30−
31− /*
32− * This is the header displayed, like "Right edge".
33− */
34− property alias title: titleLabel.text
35−
36− /*
37− * This is the block of text displayed below the header.
38− */
39− property alias text: textLabel.text
40−
41− /*
42− * This is the text for the skip button.
43− */
44− property alias skipText: skipLabel.text
45−
46− /*
47− * This is the visible status of the skip button.
48− */
49− property alias showSkip: skipLabel.visible
50−
51− /*
52− * Whether this demo is running currently.
53− */
54− readonly property bool active: available && visible
55−
56− /*
57− * Whether animations are paused.
58− */
59− property alias paused: wholeAnimation.paused
60−
61− /*
62− * Whether animations are running.
63− */
64− readonly property alias running: wholeAnimation.running
65−
66− signal skip()
67−
68− function doSkip() {
69− d.skipOnHide = true;
70− hide();
71− }
72−
73− function hideNow() {
74− overlay.visible = false;
75− overlay.available = false;
76− if (d.skipOnHide) {
77− overlay.skip();
78− }
79− }
80−
81− showAnimation: StandardAnimation {
82− property: "opacity"
83− to: 1
84− onRunningChanged: if (running) overlay.visible = true
85− }
86− hideAnimation: StandardAnimation {
87− property: "opacity"
88− to: 0
89− duration: UbuntuAnimation.BriskDuration
90− onRunningChanged: if (!running) overlay.hideNow()
91− }
92−
93− QtObject {
94− id: d
95− property bool skipOnHide: false
96− property int edgeMargin: units.gu(4)
97− }
98−
99− Rectangle {
100− objectName: "backgroundShade"
101−
102− anchors.fill: parent
103− color: "black"
104− opacity: 0.8
105− visible: overlay.active
106−
107− MouseArea {
108− objectName: "backgroundShadeMouseArea"
109−
110− anchors.fill: parent
111− enabled: overlay.edge == "none" && overlay.opacity == 1.0
112− onClicked: overlay.doSkip()
113− }
114− }
115−
116− Item {
117− id: hintGroup
118− x: 0
119− y: 0
120− width: parent.width
121− height: parent.height
122− visible: overlay.active
123−
124− Column {
125− id: labelGroup
126− spacing: units.gu(3)
127−
128− anchors {
129− margins: d.edgeMargin
130− left: parent.left
131− top: overlay.edge == "bottom" ? undefined : parent.top
132− bottom: overlay.edge == "bottom" ? parent.bottom : undefined
133− }
134−
135− Label {
136− id: titleLabel
137− fontSize: "x-large"
138− width: units.gu(25)
139− wrapMode: Text.WordWrap
140− }
141−
142− Label {
143− id: textLabel
144− width: units.gu(25)
145− wrapMode: Text.WordWrap
146− }
147−
148− Label {
149− id: skipLabel
150− objectName: "skipLabel"
151− text: i18n.tr("Skip intro")
152− color: UbuntuColors.orange
153− fontSize: "small"
154−
155− Icon {
156− anchors.left: parent.right
157− anchors.verticalCenter: parent.verticalCenter
158− height: units.dp(12)
159− width: units.dp(12)
160− name: "chevron"
161− color: UbuntuColors.orange
162− }
163−
164− MouseArea {
165− // Make clickable area bigger than just the link because
166− // otherwise, the edge demo will feel hard to dismiss.
167− anchors.fill: parent
168− anchors.margins: -units.gu(5)
169− onClicked: overlay.doSkip()
170− }
171− }
172− }
173−
174− LinearGradient {
175− id: edgeHint
176− property int size: 1
177− cached: false
178− visible: overlay.edge != "none"
179− gradient: Gradient {
180− GradientStop {
181− position: 0.0
182− color: Qt.hsla(16.0/360.0, 0.83, 0.47, 0.4) // UbuntuColors.orange, but transparent
183− }
184− GradientStop {
185− position: 1.0
186− color: "transparent"
187− }
188− }
189− anchors.fill: parent
190− start: {
191− if (overlay.edge == "right") {
192− return Qt.point(width, 0);
193− } else if (overlay.edge == "left") {
194− return Qt.point(0, 0);
195− } else if (overlay.edge == "top") {
196− return Qt.point(0, 0);
197− } else {
198− return Qt.point(0, height);
199− }
200− }
201− end: {
202− if (overlay.edge == "right") {
203− return Qt.point(width - size, 0);
204− } else if (overlay.edge == "left") {
205− return Qt.point(size, 0);
206− } else if (overlay.edge == "top") {
207− return Qt.point(0, size);
208− } else {
209− return Qt.point(0, height - size);
210− }
211− }
212− }
213− }
214−
215− SequentialAnimation {
216− id: wholeAnimation
217− objectName: "wholeAnimation"
218− running: overlay.active
219−
220− ParallelAnimation {
221− id: fadeInAnimation
222−
223− StandardAnimation {
224− target: labelGroup
225− property: {
226− if (overlay.edge == "right" || overlay.edge == "left") {
227− return "anchors.leftMargin";
228− } else if (overlay.edge == "bottom") {
229− return "anchors.bottomMargin";
230− } else {
231− return "anchors.topMargin";
232− }
233− }
234− from: {
235− if (overlay.edge == "right") {
236− return d.edgeMargin + units.gu(3)
237− } else {
238− return d.edgeMargin - units.gu(3)
239− }
240− }
241− to: d.edgeMargin
242− duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
243− }
244− StandardAnimation {
245− target: overlay
246− property: "opacity"
247− from: 0.0
248− to: 1.0
249− duration: UbuntuAnimation.SleepyDuration
250− }
251− }
252−
253− SequentialAnimation {
254− id: hintAnimation
255− loops: Animation.Infinite
256− property string prop: (overlay.edge == "left" || overlay.edge == "right") ? "x" : "y"
257− property double endVal: units.dp(5) * ((overlay.edge == "left" || overlay.edge == "top") ? 1 : -1)
258− property double maxGlow: units.dp(20)
259− property int duration: overlay.edge == "none" ? 0 : UbuntuAnimation.SleepyDuration
260−
261− ParallelAnimation {
262− StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: 0; to: hintAnimation.endVal; duration: hintAnimation.duration }
263− StandardAnimation { target: edgeHint; property: "size"; from: 1; to: hintAnimation.maxGlow; duration: hintAnimation.duration }
264− }
265−
266− // Undo the above
267− ParallelAnimation {
268− StandardAnimation { target: hintGroup; property: hintAnimation.prop; from: hintAnimation.endVal; to: 0; duration: hintAnimation.duration }
269− StandardAnimation { target: edgeHint; property: "size"; from: hintAnimation.maxGlow; to: 1; duration: hintAnimation.duration }
270− }
271− }
272− }
273−}
qml/Launcher/Launcher.qml+13−7
@@ -26,6 +26,7 @@ Item {
2626 property bool autohideEnabled: false
2727 property bool available: true // can be used to disable all interactions
2828 property alias inverted: panel.inverted
29+ property bool shadeBackground: true // can be used to disable background shade when launcher is visible
2930
3031 property int panelWidth: units.gu(8)
3132 property int dragAreaWidth: units.gu(1)
@@ -33,6 +34,10 @@ Item {
3334 property real progress: dragArea.dragging && dragArea.touchX > panelWidth ?
3435 (width * (dragArea.touchX-panelWidth) / (width - panelWidth)) : 0
3536
37+ readonly property bool dragging: dragArea.dragging
38+ readonly property real dragDistance: dragArea.dragging ? dragArea.touchX : 0
39+ readonly property real visibleWidth: panel.width + panel.x
40+
3641 readonly property bool shown: panel.x > -panel.width
3742
3843 // emitted when an application is selected
@@ -154,7 +159,7 @@ Item {
154159
155160 MouseArea {
156161 id: launcherDragArea
157− enabled: root.state == "visible"
162+ enabled: root.available && root.state == "visible"
158163 anchors.fill: panel
159164 anchors.rightMargin: -units.gu(2)
160165 drag {
@@ -180,7 +185,7 @@ Item {
180185 right: parent.right
181186 bottom: parent.bottom
182187 }
183− enabled: root.state == "visible"
188+ enabled: root.shadeBackground && root.state == "visible"
184189 onPressed: {
185190 root.state = ""
186191 }
@@ -190,7 +195,7 @@ Item {
190195 id: backgroundShade
191196 anchors.fill: parent
192197 color: "black"
193− opacity: root.state == "visible" ? 0.6 : 0
198+ opacity: root.shadeBackground && root.state == "visible" ? 0.6 : 0
194199
195200 Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.BriskDuration } }
196201 }
@@ -198,14 +203,14 @@ Item {
198203 LauncherPanel {
199204 id: panel
200205 objectName: "launcherPanel"
201− enabled: root.available
206+ enabled: root.available && root.state == "visible"
202207 width: root.panelWidth
203208 anchors {
204209 top: parent.top
205210 bottom: parent.bottom
206211 }
207212 x: -width
208− visible: x > -width || dragArea.status === DirectionalDragArea.Undecided
213+ visible: root.x > 0 || x > -width || dragArea.status === DirectionalDragArea.Undecided
209214 model: LauncherModel
210215
211216 property bool animate: true
@@ -247,6 +252,7 @@ Item {
247252 direction: Direction.Rightwards
248253
249254 enabled: root.available
255+ x: -root.x // so if launcher is adjusted relative to screen, we stay put (like tutorial does when teasing)
250256 width: root.dragAreaWidth
251257 height: root.height
252258
@@ -260,7 +266,7 @@ Item {
260266 // would appear right next to the user's finger out of nowhere.
261267 // Instead, we make the panel go towards the user's finger in several
262268 // steps. ie., in an animated way.
263− var targetPanelX = Math.min(0, touchX - panel.width)
269+ var targetPanelX = Math.min(0, touchX - panel.width) - root.x
264270 var delta = targetPanelX - panel.x
265271 // the trick is not to go all the way (1.0) as it would cause a sudden jump
266272 panel.x += 0.4 * delta
@@ -292,7 +298,7 @@ Item {
292298 name: "visible"
293299 PropertyChanges {
294300 target: panel
295− x: 0
301+ x: -root.x // so we never go past panelWidth, even when teased by tutorial
296302 }
297303 },
298304 State {
qml/Shell.qml+32−20
@@ -34,6 +34,7 @@ import "Panel"
3434import "Components"
3535import "Notifications"
3636import "Stages"
37+import "Tutorial"
3738import "Wizard"
3839import Unity.Notifications 1.0 as NotificationBackend
3940import Unity.Session 0.1
@@ -151,7 +152,7 @@ Item {
151152
152153 ScreenGrabber {
153154 id: screenGrabber
154− z: edgeDemo.z + 10
155+ z: dialogs.z + 10
155156 enabled: Powerd.status === Powerd.On
156157 }
157158
@@ -229,11 +230,11 @@ Item {
229230 }
230231
231232 onApplicationAdded: {
232− if (edgeDemo.running && appId != "unity8-dash") {
233+ if (tutorial.running && appId != "unity8-dash") {
233234 // If this happens on first boot, we may be in edge
234235 // tutorial or wizard while receiving a call. But a call
235236 // is more important than wizard so just bail out of those.
236− edgeDemo.hideEdgeDemos();
237+ tutorial.finish();
237238 wizard.hide();
238239 }
239240
@@ -278,12 +279,12 @@ Item {
278279 Binding {
279280 target: applicationsDisplayLoader.item
280281 property: "interactive"
281− value: edgeDemo.stagesEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
282+ value: tutorial.spreadEnabled && !greeter.shown && panel.indicators.fullyClosed && launcher.progress == 0 && !notifications.useModal
282283 }
283284 Binding {
284285 target: applicationsDisplayLoader.item
285286 property: "spreadEnabled"
286− value: edgeDemo.stagesEnabled && !greeter.hasLockedApp
287+ value: tutorial.spreadEnabled && !greeter.hasLockedApp
287288 }
288289 Binding {
289290 target: applicationsDisplayLoader.item
@@ -346,7 +347,7 @@ Item {
346347 hides: [launcher, panel.indicators]
347348 tabletMode: shell.sideStageEnabled
348349 launcherOffset: launcher.progress
349− forcedUnlock: edgeDemo.running
350+ forcedUnlock: tutorial.running
350351 background: shell.background
351352
352353 anchors.fill: parent
@@ -362,7 +363,11 @@ Item {
362363 launcher.hide();
363364 }
364365
365− onTease: launcher.tease()
366+ onTease: {
367+ if (!tutorial.running) {
368+ launcher.tease();
369+ }
370+ }
366371
367372 onEmergencyCall: startLockedApp("dialer-app")
368373
@@ -407,7 +412,7 @@ Item {
407412
408413 onStatusChanged: {
409414 if (Powerd.status === Powerd.Off && reason !== Powerd.Proximity &&
410− !callManager.hasCalls && !edgeDemo.running) {
415+ !callManager.hasCalls && !tutorial.running) {
411416 // We don't want to simply call greeter.showNow() here, because
412417 // that will take too long. Qt will delay button event
413418 // handling until the greeter is done loading and may think the
@@ -423,7 +428,7 @@ Item {
423428 }
424429
425430 function showHome() {
426− if (edgeDemo.running) {
431+ if (tutorial.running) {
427432 return
428433 }
429434
@@ -458,8 +463,8 @@ Item {
458463 anchors.fill: parent //because this draws indicator menus
459464 indicators {
460465 hides: [launcher]
461− available: edgeDemo.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
462− contentEnabled: edgeDemo.panelContentEnabled
466+ available: tutorial.panelEnabled && (!greeter.locked || AccountsService.enableIndicatorsWhileLocked) && !greeter.hasLockedApp
467+ contentEnabled: tutorial.panelContentEnabled
463468 width: parent.width > units.gu(60) ? units.gu(40) : parent.width
464469
465470 minimizedPanelHeight: units.gu(3)
@@ -492,8 +497,9 @@ Item {
492497 anchors.bottom: parent.bottom
493498 width: parent.width
494499 dragAreaWidth: shell.edgeSize
495− available: edgeDemo.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
500+ available: tutorial.launcherEnabled && (!greeter.locked || AccountsService.enableLauncherWhileLocked) && !greeter.hasLockedApp
496501 inverted: usageModeSettings.usageMode === "Staged"
502+ shadeBackground: !tutorial.running
497503
498504 onShowDashHome: showHome()
499505 onDash: showDash()
@@ -503,7 +509,7 @@ Item {
503509 }
504510 }
505511 onLauncherApplicationSelected: {
506− if (!edgeDemo.running) {
512+ if (!tutorial.running) {
507513 greeter.notifyAboutToFocusApp(appId);
508514 shell.activateApplication(appId)
509515 }
@@ -578,15 +584,21 @@ Item {
578584 }
579585 }
580586
581− EdgeDemo {
582− id: edgeDemo
583− objectName: "edgeDemo"
584− z: dialogs.z + 10
585− paused: Powerd.status === Powerd.Off || wizard.active // Saves power
586− greeter: greeter
587+ Tutorial {
588+ id: tutorial
589+ objectName: "tutorial"
590+ active: AccountsService.demoEdges
591+ paused: LightDM.Greeter.active
587592 launcher: launcher
588593 panel: panel
589594 stages: stages
595+ overlay: overlay
596+ edgeSize: shell.edgeSize
597+
598+ onFinished: {
599+ AccountsService.demoEdges = false;
600+ active = false; // for immediate response / if AS is having problems
601+ }
590602 }
591603
592604 Connections {
@@ -596,7 +608,7 @@ Item {
596608
597609 Rectangle {
598610 id: shutdownFadeOutRectangle
599− z: edgeDemo.z + 10
611+ z: screenGrabber.z + 10
600612 enabled: false
601613 visible: false
602614 color: "black"
qml/Stages/ApplicationWindow.qml+14−1
@@ -43,6 +43,7 @@ Item {
4343 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
4444 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
4545 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
46+ readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : ""
4647
4748 // Whether the Application had a surface before but lost it.
4849 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
@@ -72,7 +73,7 @@ Item {
7273 Image {
7374 id: screenshotImage
7475 objectName: "screenshotImage"
75− source: ""
76+ source: d.defaultScreenshot
7677 anchors.fill: parent
7778 antialiasing: !root.interactive
7879
@@ -123,6 +124,7 @@ Item {
123124 }
124125
125126 StateGroup {
127+ id: stateGroup
126128 objectName: "applicationWindowStateGroup"
127129 states: [
128130 State {
@@ -215,6 +217,17 @@ Item {
215217 PropertyAction { target: screenshotImage; property: "source"; value: "" }
216218 }
217219 },
220+ Transition {
221+ from: "splashScreen"; to: "screenshot"
222+ SequentialAnimation {
223+ PropertyAction { target: screenshotImage
224+ property: "visible"; value: true }
225+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
226+ from: 0.0; to: 1.0
227+ duration: UbuntuAnimation.BriskDuration }
228+ PropertyAction { target: splashLoader; property: "active"; value: false }
229+ }
230+ },
218231 Transition {
219232 from: "surface"; to: "void"
220233 SequentialAnimation {
qml/Stages/PhoneStage.qml+62−34
@@ -32,6 +32,21 @@ Rectangle {
3232 property bool spreadEnabled: true // If false, animations and right edge will be disabled
3333 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
3434 property int orientation: Qt.PortraitOrientation
35+ property QtObject applicationManager: ApplicationManager
36+ property bool focusFirstApp: true // If false, focused app will appear on right edge like other apps
37+ property bool altTabEnabled: true
38+ property real startScale: 1.1
39+ property real endScale: 0.7
40+
41+ // How far left the stage has been dragged
42+ readonly property real dragProgress: spreadRepeater.count > 0 ? -spreadRepeater.itemAt(0).xTranslate : 0
43+
44+ readonly property alias dragging: spreadDragArea.dragging
45+
46+ // Only used by the tutorial right now, when it is teasing the right edge
47+ property real dragAreaOverlap
48+
49+ signal opened()
3550
3651 color: "#111111"
3752
@@ -52,30 +67,30 @@ Rectangle {
5267 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
5368 // left edge drag released. Minimum distance is given by design.
5469 if (priv.oldInverseProgress > units.gu(22)) {
55− ApplicationManager.requestFocusApplication("unity8-dash");
70+ applicationManager.requestFocusApplication("unity8-dash");
5671 }
5772 }
5873 priv.oldInverseProgress = inverseProgress;
5974 }
6075
6176 Connections {
62− target: ApplicationManager
77+ target: applicationManager
6378
6479 onFocusRequested: {
6580 if (spreadView.phase > 0) {
6681 spreadView.snapTo(priv.indexOf(appId));
6782 } else {
68− ApplicationManager.focusApplication(appId);
83+ applicationManager.focusApplication(appId);
6984 }
7085 }
7186
7287 onApplicationAdded: {
7388 if (spreadView.phase == 2) {
74− spreadView.snapTo(ApplicationManager.count - 1);
89+ spreadView.snapTo(applicationManager.count - 1);
7590 } else {
7691 spreadView.phase = 0;
7792 spreadView.contentX = -spreadView.shift;
78− ApplicationManager.focusApplication(appId);
93+ applicationManager.focusApplication(appId);
7994 }
8095 }
8196
@@ -90,9 +105,9 @@ Rectangle {
90105 }
91106
92107 function focusTopMostApp() {
93− if (ApplicationManager.count > 0) {
94− var topmostApp = ApplicationManager.get(0);
95− ApplicationManager.focusApplication(topmostApp.appId);
108+ if (applicationManager.count > 0) {
109+ var topmostApp = applicationManager.get(0);
110+ applicationManager.focusApplication(topmostApp.appId);
96111 }
97112 }
98113 }
@@ -100,18 +115,19 @@ Rectangle {
100115 QtObject {
101116 id: priv
102117
103− property string focusedAppId: ApplicationManager.focusedApplicationId
104− property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
118+ readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
119+ property string focusedAppId: applicationManager.focusedApplicationId
120+ property var focusedApplication: applicationManager.findApplication(focusedAppId)
105121 property var focusedAppDelegate: null
106122
107123 property real oldInverseProgress: 0
108− property bool animateX: true
124+ property bool animateX: false
109125
110126 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
111127
112128 function indexOf(appId) {
113− for (var i = 0; i < ApplicationManager.count; i++) {
114− if (ApplicationManager.get(i).appId == appId) {
129+ for (var i = 0; i < applicationManager.count; i++) {
130+ if (applicationManager.get(i).appId == appId) {
115131 return i;
116132 }
117133 }
@@ -131,7 +147,7 @@ Rectangle {
131147
132148 // This indicates when the spreadView is active. That means, all the animations
133149 // are activated and tiles need to line up for the spread.
134− readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized
150+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
135151
136152 // The flickable needs to fill the screen in order to get touch events all over.
137153 // However, we don't want to the user to be able to scroll back all the way. For
@@ -197,10 +213,17 @@ Rectangle {
197213 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
198214 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
199215 snapAnimation.start();
216+ root.opened();
200217 }
201218 }
202219 function snapTo(index) {
203− if (ApplicationManager.count <= index) {
220+ if (!root.altTabEnabled) {
221+ // Reset to start instead
222+ snapAnimation.targetContentX = -shift;
223+ snapAnimation.start();
224+ return;
225+ }
226+ if (applicationManager.count <= index) {
204227 // In case we're trying to snap to some non existing app, lets snap back to the first one
205228 index = 0;
206229 }
@@ -215,10 +238,12 @@ Rectangle {
215238 snapAnimation.start();
216239 }
217240
218− // In case the ApplicationManager already holds an app when starting up we're missing animations
241+ // In case the applicationManager already holds an app when starting up we're missing animations
219242 // Make sure we end up in the same state
220243 Component.onCompleted: {
221244 spreadView.contentX = -spreadView.shift
245+ priv.animateX = true;
246+ snapAnimation.complete();
222247 }
223248
224249 SequentialAnimation {
@@ -235,7 +260,7 @@ Rectangle {
235260 ScriptAction {
236261 script: {
237262 if (spreadView.selectedIndex >= 0) {
238− ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
263+ applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId);
239264
240265 spreadView.selectedIndex = -1;
241266 spreadView.phase = 0;
@@ -250,7 +275,7 @@ Rectangle {
250275 // This width controls how much the spread can be flicked left/right. It's composed of:
251276 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
252277 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
253− width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
278+ width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
254279 height: parent.height
255280 Behavior on width {
256281 enabled: spreadView.closingIndex >= 0
@@ -265,20 +290,22 @@ Rectangle {
265290 x: spreadView.contentX
266291
267292 onClicked: {
268− spreadView.snapTo(0);
293+ if (root.altTabEnabled) {
294+ spreadView.snapTo(0);
295+ }
269296 }
270297
271298 Repeater {
272299 id: spreadRepeater
273300 objectName: "spreadRepeater"
274− model: ApplicationManager
301+ model: applicationManager
275302 delegate: TransformedSpreadDelegate {
276303 id: appDelegate
277304 objectName: "appDelegate" + index
278305 startAngle: 45
279306 endAngle: 5
280− startScale: 1.1
281− endScale: 0.7
307+ startScale: root.startScale
308+ endScale: root.endScale
282309 startDistance: spreadView.tileDistance
283310 endDistance: units.gu(.5)
284311 width: spreadView.width
@@ -286,11 +313,12 @@ Rectangle {
286313 selected: spreadView.selectedIndex == index
287314 otherSelected: spreadView.selectedIndex >= 0 && !selected
288315 interactive: !spreadView.interactive && spreadView.phase === 0
289− && spreadView.shiftedContentX === 0 && root.interactive && index === 0
290− swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
316+ && spreadView.shiftedContentX === 0 && root.interactive && isFocused
317+ swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
291318 maximizedAppTopMargin: root.maximizedAppTopMargin
292319 dropShadow: spreadView.active ||
293320 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
321+ focusFirstApp: root.focusFirstApp
294322
295323 readonly property bool isDash: model.appId == "unity8-dash"
296324
@@ -298,7 +326,7 @@ Rectangle {
298326
299327 x: {
300328 // focused app is always positioned at 0 except when following left edge drag
301− if (index == 0) {
329+ if (isFocused) {
302330 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
303331 return root.inverseProgress;
304332 }
@@ -309,10 +337,10 @@ Rectangle {
309337 }
310338
311339 // Otherwise line up for the spread
312− return spreadView.width + (index - 1) * spreadView.tileDistance;
340+ return spreadView.width + spreadIndex * spreadView.tileDistance;
313341 }
314342
315− application: ApplicationManager.get(index)
343+ application: applicationManager.get(index)
316344 closeable: !isDash
317345
318346 property real behavioredIndex: index
@@ -350,7 +378,7 @@ Rectangle {
350378 progress: {
351379 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
352380 // Tile 1 needs to move directly from the beginning...
353− if (behavioredIndex == 1 && spreadView.phase < 2) {
381+ if (root.focusFirstApp && behavioredIndex == 1 && spreadView.phase < 2) {
354382 tileProgress += spreadView.tileDistance / spreadView.width;
355383 }
356384 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
@@ -365,7 +393,7 @@ Rectangle {
365393
366394 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
367395 animatedProgress: {
368− if (spreadView.phase == 0 && index < 2) {
396+ if (spreadView.phase == 0 && index <= priv.firstSpreadIndex) {
369397 if (progress < spreadView.positionMarker1) {
370398 return progress;
371399 } else if (progress < spreadView.positionMarker1 + 0.05){
@@ -390,11 +418,11 @@ Rectangle {
390418 }
391419
392420 onClicked: {
393− if (spreadView.phase == 2) {
394− if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
421+ if (root.altTabEnabled && spreadView.phase == 2) {
422+ if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) {
395423 spreadView.snapTo(index);
396424 } else {
397− ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
425+ applicationManager.requestFocusApplication(applicationManager.get(index).appId);
398426 }
399427 }
400428 }
@@ -409,7 +437,7 @@ Rectangle {
409437
410438 onClosed: {
411439 spreadView.closingIndex = index;
412− ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
440+ applicationManager.stopApplication(applicationManager.get(index).appId);
413441 }
414442 }
415443 }
@@ -422,7 +450,7 @@ Rectangle {
422450 direction: Direction.Leftwards
423451 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
424452
425− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
453+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
426454 width: root.dragAreaWidth
427455
428456 property var gesturePoints: new Array()
qml/Stages/TransformedSpreadDelegate.qml+27−21
@@ -46,6 +46,18 @@ SpreadDelegate {
4646 property real startDistance: units.gu(5)
4747 property real endDistance: units.gu(.5)
4848
49+ // Whether first app is displayed entirely vs just on the spread edge
50+ property bool focusFirstApp: true
51+
52+ // Whether this delegate is displayed to the user outside of the spread
53+ readonly property bool isFocused: focusFirstApp && index === 0
54+
55+ // Adjusted index for any spread animation calculations
56+ readonly property int spreadIndex: index - (focusFirstApp ? 1 : 0)
57+
58+ // How far left this delegate has been translated
59+ readonly property alias xTranslate: priv.xTranslate
60+
4961 onSelectedChanged: {
5062 if (selected) {
5163 priv.snapshot();
@@ -63,8 +75,8 @@ SpreadDelegate {
6375 Connections {
6476 target: spreadView
6577 onPhaseChanged: {
66− if (spreadView.phase == 1) {
67− if (index == 0) {
78+ if (root.focusFirstApp && spreadView.phase == 1) {
79+ if (index === 0) {
6880 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
6981 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
7082 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
@@ -134,7 +146,7 @@ SpreadDelegate {
134146 }
135147
136148 if (otherSelected) {
137− if (spreadView.phase < 2 && index == 0) {
149+ if (spreadView.phase < 2 && root.isFocused) {
138150 return linearAnimation(selectedProgress, 0, selectedXTranslate,
139151 selectedXTranslate - spreadView.tileDistance, root.progress);
140152 }
@@ -142,8 +154,7 @@ SpreadDelegate {
142154 return selectedXTranslate;
143155 }
144156
145− switch (index) {
146− case 0:
157+ if (root.focusFirstApp && index === 0) {
147158 if (spreadView.phase == 0) {
148159 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
149160 0, -spreadView.width * .25, root.animatedProgress));
@@ -153,11 +164,10 @@ SpreadDelegate {
153164 } else if (!priv.isSelected){ // phase 2
154165 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
155166 return -easingCurve.value * spreadView.width + spreadView.width;
156− } else if (priv.isSelected) {
167+ } else {
157168 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
158169 }
159−
160− case 1:
170+ } else if (root.focusFirstApp && index === 1) {
161171 if (spreadView.phase == 0 && !priv.isSelected) {
162172 return linearAnimation(0, spreadView.positionMarker2,
163173 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
@@ -170,13 +180,13 @@ SpreadDelegate {
170180
171181 if (priv.isSelected) {
172182 // Distance to left edge
173− var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
183+ var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance);
174184 return linearAnimation(selectedProgress, 0,
175185 selectedXTranslate, targetTranslate, root.progress);
176186 }
177187
178188 // Fix it at the right edge...
179− var rightEdgeOffset = -((index - 1) * root.startDistance);
189+ var rightEdgeOffset = -(root.spreadIndex * root.startDistance);
180190 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
181191 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
182192 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
@@ -194,8 +204,7 @@ SpreadDelegate {
194204 if (priv.isSelected) {
195205 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
196206 }
197− switch (index) {
198− case 0:
207+ if (root.focusFirstApp && index === 0) {
199208 if (spreadView.phase == 0) {
200209 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
201210 0, root.tile0SnapAngle, root.animatedProgress));
@@ -203,7 +212,7 @@ SpreadDelegate {
203212 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
204213 root.tile0SnapAngle, phase2startAngle, root.progress);
205214 }
206− case 1:
215+ } else if (root.focusFirstApp && index === 1) {
207216 if (spreadView.phase == 0) {
208217 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
209218 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -227,15 +236,14 @@ SpreadDelegate {
227236 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
228237 }
229238
230− switch (index) {
231− case 0:
239+ if (root.focusFirstApp && index === 0) {
232240 if (spreadView.phase == 0) {
233241 return 1;
234242 } else if (spreadView.phase == 1) {
235243 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
236244 1, phase2startScale, root.progress);
237245 }
238− case 1:
246+ } else if (root.focusFirstApp && index === 1) {
239247 if (spreadView.phase == 0) {
240248 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
241249 return linearAnimation(0, spreadView.positionMarker2,
@@ -254,7 +262,7 @@ SpreadDelegate {
254262 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
255263 selectedOpacity, 0, root.progress);
256264 }
257− if (index == 0) {
265+ if (root.isFocused) {
258266 switch (spreadView.phase) {
259267 case 0:
260268 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
@@ -272,16 +280,14 @@ SpreadDelegate {
272280 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
273281 }
274282
275− switch (index) {
276− case 0:
283+ if (root.focusFirstApp && index === 0) {
277284 if (spreadView.phase == 0) {
278285 return 0;
279286 } else if (spreadView.phase == 1) {
280287 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
281288 0, priv.phase2startTopMarginProgress, root.progress);
282289 }
283− break;
284− case 1:
290+ } else if (root.focusFirstApp && index === 1) {
285291 if (spreadView.phase == 0) {
286292 return 0;
287293 } else if (spreadView.phase == 1) {
qml/Tutorial/Arrow.qmlnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property alias color: circle.color
24+
25+ // Will make whole arrow darker
26+ property real darkenBy: 0
27+
28+ property alias chevronOpacity: chevron.opacity
29+
30+ ////
31+
32+ Rectangle {
33+ id: circle
34+ anchors.fill: parent
35+ radius: width / 2
36+ }
37+
38+ Image {
39+ id: chevron
40+ anchors.centerIn: parent
41+ source: Qt.resolvedUrl("graphics/chevron.png")
42+ fillMode: Image.PreserveAspectFit
43+ sourceSize.width: 152
44+ sourceSize.height: 152
45+ width: parent.width / 2
46+ height: parent.height / 2
47+ }
48+
49+ Rectangle {
50+ id: darkCircle
51+ anchors.fill: parent
52+ radius: width / 2
53+ color: "black"
54+ opacity: root.darkenBy
55+ }
56+}
qml/Tutorial/Slider.qmlnew file+123
@@ -0,0 +1,123 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ // Whether this slider is short or long
24+ property bool shortSwipe
25+
26+ // How far the user has slid
27+ property real offset
28+
29+ // Set to true when slider is being used
30+ property bool active
31+
32+ // How far in percentage terms
33+ readonly property real percent: d.slideOffset / target.x
34+
35+ QtObject {
36+ id: d
37+ readonly property color trayColor: "#424141"
38+ readonly property real margin: units.gu(0.5)
39+ readonly property real arrowSize: root.height - margin * 2
40+ readonly property real dotSize: units.dp(1)
41+ readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
42+ readonly property real offscreenOffset: units.gu(2)
43+ }
44+
45+ implicitWidth: shortSwipe ? units.gu(15) : units.gu(27.5)
46+ implicitHeight: units.gu(6.5)
47+
48+ Rectangle {
49+ color: d.trayColor
50+ anchors.fill: parent
51+ anchors.rightMargin: clipBox.width - 1
52+ }
53+
54+ // We want to have a circular border around the target. But we can't just
55+ // do a radius on two of a rectangle's corners. So we clip a full circle.
56+ Item {
57+ id: clipBox
58+
59+ clip: true
60+ anchors.top: parent.top
61+ anchors.bottom: parent.bottom
62+ anchors.right: parent.right
63+ width: parent.height / 2
64+
65+ Rectangle {
66+ color: d.trayColor
67+ anchors.top: parent.top
68+ anchors.bottom: parent.bottom
69+ anchors.right: parent.right
70+ width: parent.width * 2
71+ radius: parent.width
72+ }
73+ }
74+
75+ Arrow {
76+ id: target
77+ width: d.arrowSize
78+ height: d.arrowSize
79+ color: "#73000000"
80+ chevronOpacity: 0.52
81+ anchors.right: parent.right
82+ anchors.rightMargin: d.margin
83+ anchors.verticalCenter: parent.verticalCenter
84+ }
85+
86+ Row {
87+ anchors.left: handle.horizontalCenter
88+ anchors.right: target.horizontalCenter
89+ anchors.verticalCenter: parent.verticalCenter
90+
91+ layoutDirection: Qt.RightToLeft
92+ spacing: d.dotSize * 2
93+
94+ Repeater {
95+ model: parent.width / (parent.spacing + d.dotSize)
96+ Rectangle {
97+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
98+ height: d.dotSize
99+ width: height
100+ radius: width
101+ color: "white"
102+ opacity: 0.2
103+ }
104+ }
105+ }
106+
107+ Arrow {
108+ id: handle
109+ width: d.arrowSize
110+ height: d.arrowSize
111+ color: UbuntuColors.orange
112+ darkenBy: root.active ? 0.5 : 0
113+ anchors.left: parent.left
114+ // We use a Translate transform rather than anchors.leftMargin because
115+ // the latter has weird performance problems on the TutorialRight page.
116+ transform: [
117+ Translate {
118+ x: d.slideOffset
119+ }
120+ ]
121+ anchors.verticalCenter: parent.verticalCenter
122+ }
123+}
qml/Tutorial/Tick.qmlnew file+29
@@ -0,0 +1,29 @@
1+/*
2+ * Copyright (C) 2015 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+MouseArea {
21+ implicitHeight: tick.height
22+ implicitWidth: tick.width
23+ Image {
24+ id: tick
25+ source: Qt.resolvedUrl("graphics/tick.png")
26+ height: units.gu(6.5)
27+ width: units.gu(6.5)
28+ }
29+}
qml/Tutorial/Tutorial.qmlnew file+92
@@ -0,0 +1,92 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property alias active: loader.active
24+ property bool paused
25+ property real edgeSize
26+
27+ property Item launcher
28+ property Item panel
29+ property Item stages
30+ property Item overlay
31+
32+ readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
33+ readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
34+ readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
35+ readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
36+ readonly property bool running: loader.item ? loader.item.running : false
37+
38+ function finish() {
39+ if (loader.item) {
40+ loader.item.finish();
41+ }
42+ }
43+
44+ signal finished()
45+
46+ Loader {
47+ id: loader
48+ anchors.fill: parent
49+ source: "TutorialContent.qml"
50+
51+ Binding {
52+ target: loader.item
53+ property: "paused"
54+ value: root.paused
55+ }
56+
57+ Binding {
58+ target: loader.item
59+ property: "edgeSize"
60+ value: root.edgeSize
61+ }
62+
63+ Binding {
64+ target: loader.item
65+ property: "launcher"
66+ value: root.launcher
67+ }
68+
69+ Binding {
70+ target: loader.item
71+ property: "panel"
72+ value: root.panel
73+ }
74+
75+ Binding {
76+ target: loader.item
77+ property: "stages"
78+ value: root.stages
79+ }
80+
81+ Binding {
82+ target: loader.item
83+ property: "overlay"
84+ value: root.overlay
85+ }
86+
87+ Connections {
88+ target: loader.item
89+ onFinished: root.finished()
90+ }
91+ }
92+}
qml/Tutorial/TutorialBottom.qmlnew file+104
@@ -0,0 +1,104 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import "../Components"
21+import "." as LocalComponents
22+
23+TutorialPage {
24+ id: root
25+
26+ property alias edgeSize: dragArea.height
27+
28+ title: i18n.tr("Open special menus")
29+ text: i18n.tr("Swipe up from the bottom edge.")
30+ fullTextWidth: true
31+
32+ SequentialAnimation {
33+ id: teaseAnimation
34+ paused: running && root.paused
35+ running: !dragArea.useTouchY && slider.dragOffset === 0
36+ loops: Animation.Infinite
37+
38+ UbuntuNumberAnimation {
39+ target: slider
40+ property: "teaseOffset"
41+ to: units.gu(1)
42+ duration: UbuntuAnimation.SleepyDuration
43+ }
44+ UbuntuNumberAnimation {
45+ target: slider
46+ property: "teaseOffset"
47+ to: 0
48+ duration: UbuntuAnimation.SleepyDuration
49+ }
50+ }
51+
52+ foreground {
53+ children: [
54+ LocalComponents.Slider {
55+ id: slider
56+ anchors {
57+ bottom: parent.bottom
58+ bottomMargin: width / 2 - height / 2
59+ horizontalCenter: parent.horizontalCenter
60+ }
61+ rotation: -90
62+ offset: teaseOffset + dragOffset
63+ active: dragArea.dragging
64+
65+ property real teaseOffset
66+ property real dragOffset: dragArea.useTouchY ? -dragArea.touchY : 0
67+
68+ Behavior on dragOffset {
69+ id: offsetAnimation
70+ UbuntuNumberAnimation {}
71+ }
72+ }
73+ ]
74+ }
75+
76+ EdgeDragArea {
77+ id: dragArea
78+ direction: Direction.Upwards
79+ anchors {
80+ bottom: parent.bottom
81+ left: parent.left
82+ right: parent.right
83+ }
84+
85+ property bool useTouchY
86+
87+ onDraggingChanged: {
88+ if (!dragging) {
89+ if (slider.percent >= 0.85) {
90+ root.hide();
91+ } else if (slider.percent >= 0.15) {
92+ root.showError();
93+ }
94+ }
95+
96+ // We use a separate vars here rather than just directly looking at
97+ // 'dragging' because we want to preserve our 'slider.offset'
98+ // value during the above percent check. Now that we made it,
99+ // we can have 'slider.offset' go back to zero.
100+ offsetAnimation.enabled = !dragging;
101+ useTouchY = dragging;
102+ }
103+ }
104+}
qml/Tutorial/TutorialBottomFinish.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ title: i18n.tr("This action does different things for different apps")
25+ text: i18n.tr("Tap here to finish.")
26+ fullTextWidth: true
27+
28+ foreground {
29+ children: [
30+ LocalComponents.Tick {
31+ objectName: "tick"
32+ anchors {
33+ horizontalCenter: parent.horizontalCenter
34+ top: parent.top
35+ topMargin: root.textBottom + units.gu(3)
36+ }
37+ onClicked: root.hide()
38+ }
39+ ]
40+ }
41+}
qml/Tutorial/TutorialContent.qmlnew file+124
@@ -0,0 +1,124 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+
20+Item {
21+ id: root
22+
23+ property Item launcher
24+ property Item panel
25+ property Item stages
26+ property Item overlay
27+
28+ readonly property bool launcherEnabled: !running ||
29+ (!paused && tutorialLeft.shown)
30+ readonly property bool spreadEnabled: !running
31+ readonly property bool panelEnabled: !running
32+ readonly property bool panelContentEnabled: !running
33+ readonly property alias running: d.running
34+
35+ property bool paused: false
36+ property real edgeSize
37+
38+ signal finished()
39+
40+ function finish() {
41+ d.stop();
42+ finished();
43+ }
44+
45+ ////
46+
47+ Component.onCompleted: {
48+ d.start();
49+ }
50+
51+ QtObject {
52+ id: d
53+
54+ property bool running
55+
56+ function stop() {
57+ running = false;
58+ }
59+
60+ function start() {
61+ running = true;
62+ tutorialLeft.show();
63+ }
64+ }
65+
66+ TutorialLeft {
67+ id: tutorialLeft
68+ objectName: "tutorialLeft"
69+ parent: root.stages
70+ anchors.fill: parent
71+ launcher: root.launcher
72+ paused: !shown || root.paused
73+
74+ onFinished: tutorialLeftFinish.show()
75+ }
76+
77+ TutorialLeftFinish {
78+ id: tutorialLeftFinish
79+ objectName: "tutorialLeftFinish"
80+ parent: root.stages
81+ anchors.fill: parent
82+ textXOffset: root.launcher.panelWidth
83+ paused: !shown || root.paused
84+
85+ onFinished: {
86+ root.launcher.hide();
87+ tutorialRight.show();
88+ }
89+ }
90+
91+ TutorialRight {
92+ id: tutorialRight
93+ objectName: "tutorialRight"
94+ parent: root.stages
95+ anchors.fill: parent
96+ edgeSize: root.edgeSize
97+ panel: root.panel
98+ paused: !shown || root.paused
99+
100+ onFinished: tutorialBottom.show()
101+ }
102+
103+ TutorialBottom {
104+ id: tutorialBottom
105+ objectName: "tutorialBottom"
106+ parent: root.stages
107+ anchors.fill: parent
108+ edgeSize: root.edgeSize
109+ paused: !shown || root.paused
110+
111+ onFinished: tutorialBottomFinish.show()
112+ }
113+
114+ TutorialBottomFinish {
115+ id: tutorialBottomFinish
116+ objectName: "tutorialBottomFinish"
117+ parent: root.stages
118+ anchors.fill: parent
119+ backgroundFadesOut: true
120+ paused: !shown || root.paused
121+
122+ onFinished: root.finish()
123+ }
124+}
qml/Tutorial/TutorialLeft.qmlnew file+91
@@ -0,0 +1,91 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ property var launcher
25+
26+ title: i18n.tr("Open the launcher")
27+ text: i18n.tr("Short swipe from the left edge.")
28+
29+ textXOffset: root.launcher.x + root.launcher.visibleWidth
30+
31+ Connections {
32+ target: root.launcher
33+
34+ onStateChanged: {
35+ if (root.launcher.state === "visible") {
36+ finishTimer.start();
37+ }
38+ }
39+
40+ onDash: {
41+ finishTimer.stop();
42+ root.showError();
43+ root.launcher.hide();
44+ }
45+ }
46+
47+ SequentialAnimation {
48+ id: teaseAnimation
49+ paused: running && root.paused
50+ running: !slider.active && root.launcher.visibleWidth === 0 && root.shown
51+ loops: Animation.Infinite
52+
53+ UbuntuNumberAnimation {
54+ target: root.launcher
55+ property: "x"
56+ to: units.gu(2)
57+ duration: UbuntuAnimation.SleepyDuration
58+ }
59+ UbuntuNumberAnimation {
60+ target: root.launcher
61+ property: "x"
62+ to: 0
63+ duration: UbuntuAnimation.SleepyDuration
64+ }
65+ }
66+
67+ Timer {
68+ id: finishTimer
69+ interval: 1
70+ onTriggered: {
71+ root.hide();
72+ root.launcher.x = 0; // make sure to reset launcher before we go
73+ }
74+ }
75+
76+ foreground {
77+ children: [
78+ LocalComponents.Slider {
79+ id: slider
80+ anchors {
81+ left: parent.left
82+ top: parent.top
83+ topMargin: root.textBottom + units.gu(3)
84+ }
85+ offset: root.launcher.x + root.launcher.visibleWidth + root.launcher.progress
86+ active: root.launcher.dragging
87+ shortSwipe: true
88+ }
89+ ]
90+ }
91+}
qml/Tutorial/TutorialLeftFinish.qmlnew file+41
@@ -0,0 +1,41 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "." as LocalComponents
20+
21+TutorialPage {
22+ id: root
23+
24+ title: i18n.tr("These are the shortcuts to favorite apps")
25+ text: i18n.tr("Tap here to continue.")
26+ fullTextWidth: true
27+
28+ foreground {
29+ children: [
30+ LocalComponents.Tick {
31+ objectName: "tick"
32+ anchors {
33+ horizontalCenter: parent.horizontalCenter
34+ top: parent.top
35+ topMargin: root.textBottom + units.gu(3)
36+ }
37+ onClicked: root.hide()
38+ }
39+ ]
40+ }
41+}
qml/Tutorial/TutorialPage.qmlnew file+245
@@ -0,0 +1,245 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import "../Components"
20+
21+Showable {
22+ id: root
23+
24+ // This is the header displayed, like "Right edge"
25+ property alias title: titleLabel.text
26+
27+ // This is the block of text displayed below the header
28+ property alias text: textLabel.text
29+
30+ // Whether animations are paused
31+ property bool paused
32+
33+ // Whether to give the text the full width that the title has
34+ property bool fullTextWidth
35+
36+ // Whether whole page (background + foreground) or just the foreground fades in
37+ property bool backgroundFadesIn: false
38+
39+ // Whether whole page (background + foreground) or just the foreground fades out
40+ property bool backgroundFadesOut: false
41+
42+ // The foreground Item, add children to it that you want to fade in
43+ property alias foreground: foregroundExtra
44+
45+ // The text label bottom, so you can position elements relative to it
46+ readonly property real textBottom: Math.max(textLabel.y + textLabel.height, errorTextLabel.y + errorTextLabel.height)
47+
48+ // The MouseArea that eats events (so you can adjust size as you will)
49+ property alias mouseArea: mouseArea
50+
51+ // X/Y offsets for text
52+ property real textXOffset: 0
53+ property real textYOffset: 0
54+
55+ // Foreground text opacity
56+ property real textOpacity: 1
57+
58+ signal finished()
59+
60+ function showError() {
61+ errorTimer.start();
62+ }
63+
64+ ////
65+
66+ visible: false
67+ shown: false
68+
69+ property real _foregroundHideOpacity
70+
71+ showAnimation: StandardAnimation {
72+ property: root.backgroundFadesIn ? "opacity" : "_foregroundHideOpacity"
73+ from: 0
74+ to: 1
75+ duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
76+ onStarted: root.visible = true
77+ }
78+
79+ hideAnimation: StandardAnimation {
80+ property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
81+ to: 0
82+ duration: UbuntuAnimation.BriskDuration
83+ onStopped: {
84+ root.visible = false;
85+ root.finished();
86+ }
87+ }
88+
89+ QtObject {
90+ id: d
91+
92+ readonly property real sideMargin: units.gu(5.5)
93+ readonly property real verticalOffset: -units.gu(9)
94+ readonly property real textXOffset: Math.max(0, root.textXOffset - sideMargin + units.gu(2))
95+
96+ property real fadeInOffset: {
97+ if (showAnimation.running) {
98+ var opacity = root[root.showAnimation.property]
99+ return (1 - opacity) * units.gu(3);
100+ } else {
101+ return 0;
102+ }
103+ }
104+ }
105+
106+ Timer {
107+ id: errorTimer
108+ interval: 3500
109+ }
110+
111+ MouseArea { // eat any errant presses
112+ id: mouseArea
113+ anchors.fill: parent
114+ }
115+
116+ Rectangle {
117+ anchors.fill: parent
118+ color: "black"
119+ opacity: 0.82
120+ }
121+
122+ Item {
123+ id: foreground
124+ anchors.fill: parent
125+ opacity: root._foregroundHideOpacity
126+
127+ Item {
128+ anchors.fill: parent
129+ opacity: root.textOpacity
130+
131+ Label {
132+ id: titleLabel
133+ anchors {
134+ top: parent.verticalCenter
135+ topMargin: d.verticalOffset + root.textYOffset
136+ left: parent.left
137+ leftMargin: d.sideMargin + d.textXOffset
138+ }
139+ width: parent.width - d.sideMargin * 2
140+ horizontalAlignment: Text.AlignLeft
141+ wrapMode: Text.Wrap
142+ font.weight: Font.Light
143+ font.pixelSize: units.gu(3.5)
144+ }
145+
146+ Label {
147+ id: textLabel
148+ anchors {
149+ top: titleLabel.bottom
150+ topMargin: units.gu(2)
151+ left: parent.left
152+ leftMargin: d.sideMargin + d.textXOffset
153+ }
154+ width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
155+ horizontalAlignment: Text.AlignLeft
156+ wrapMode: Text.Wrap
157+ font.weight: Font.Light
158+ font.pixelSize: units.gu(2.5)
159+ }
160+
161+ // We use two separate labels like this rather than just changing
162+ // the text of the above labels because we want to know where to place
163+ // sliders (via root.textBottom) without having that place change
164+ // as the text changes length.
165+ Label {
166+ id: errorTitleLabel
167+ objectName: "errorTitleLabel"
168+ anchors {
169+ top: titleLabel.top
170+ left: titleLabel.left
171+ }
172+ width: titleLabel.width
173+ horizontalAlignment: titleLabel.horizontalAlignment
174+ wrapMode: titleLabel.wrapMode
175+ font.weight: titleLabel.font.weight
176+ font.pixelSize: titleLabel.font.pixelSize
177+ opacity: 0
178+ text: i18n.tr("You almost got it!")
179+ }
180+
181+ Label {
182+ id: errorTextLabel
183+ objectName: "errorTextLabel"
184+ anchors {
185+ top: errorTitleLabel.bottom
186+ topMargin: textLabel.anchors.topMargin
187+ left: textLabel.left
188+ }
189+ width: textLabel.width
190+ horizontalAlignment: textLabel.horizontalAlignment
191+ wrapMode: textLabel.wrapMode
192+ font.weight: textLabel.font.weight
193+ font.pixelSize: textLabel.font.pixelSize
194+ opacity: 0
195+ text: i18n.tr("Try again.")
196+ }
197+ }
198+
199+ // A place for subclasses to add extra widgets
200+ Item {
201+ id: foregroundExtra
202+ anchors.fill: parent
203+ }
204+ }
205+
206+ states: State {
207+ name: "errorState"
208+ when: errorTimer.running
209+ PropertyChanges { target: titleLabel; opacity: 0 }
210+ PropertyChanges { target: textLabel; opacity: 0 }
211+ PropertyChanges { target: errorTitleLabel; opacity: 1 }
212+ PropertyChanges { target: errorTextLabel; opacity: 1 }
213+ }
214+
215+ transitions: Transition {
216+ to: "errorState"
217+ reversible: true
218+ SequentialAnimation {
219+ ParallelAnimation {
220+ StandardAnimation {
221+ target: titleLabel
222+ property: "opacity"
223+ duration: UbuntuAnimation.BriskDuration
224+ }
225+ StandardAnimation {
226+ target: textLabel
227+ property: "opacity"
228+ duration: UbuntuAnimation.BriskDuration
229+ }
230+ }
231+ ParallelAnimation {
232+ StandardAnimation {
233+ target: errorTitleLabel
234+ property: "opacity"
235+ duration: UbuntuAnimation.BriskDuration
236+ }
237+ StandardAnimation {
238+ target: errorTextLabel
239+ property: "opacity"
240+ duration: UbuntuAnimation.BriskDuration
241+ }
242+ }
243+ }
244+ }
245+}
qml/Tutorial/TutorialRight.qmlnew file+223
@@ -0,0 +1,223 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.3
18+import Ubuntu.Components 1.1
19+import Ubuntu.Gestures 0.1
20+import Unity.Application 0.1
21+import "../Components"
22+import "../Stages"
23+import "." as LocalComponents
24+
25+TutorialPage {
26+ id: root
27+
28+ property var panel
29+ property alias edgeSize: stage.dragAreaWidth
30+
31+ title: i18n.tr("To view open apps")
32+ text: i18n.tr("Long swipe from the right edge.")
33+
34+ textOpacity: 1 - slider.percent
35+
36+ SequentialAnimation {
37+ id: teaseAnimation
38+ paused: running && root.paused
39+ running: !stage.dragging && stage.dragProgress === 0
40+ loops: Animation.Infinite
41+
42+ UbuntuNumberAnimation {
43+ target: stage
44+ property: "x"
45+ to: -stage.dragAreaOverlap
46+ duration: UbuntuAnimation.SleepyDuration
47+ }
48+ UbuntuNumberAnimation {
49+ target: stage
50+ property: "x"
51+ to: 0
52+ duration: UbuntuAnimation.SleepyDuration
53+ }
54+ }
55+
56+ foreground {
57+ children: [
58+ LocalComponents.Slider {
59+ id: slider
60+ anchors {
61+ right: parent.right
62+ top: parent.top
63+ topMargin: root.textBottom + units.gu(3)
64+ }
65+ rotation: 180
66+ offset: stage.dragProgress - stage.x
67+ active: stage.dragging
68+ },
69+
70+ // Just assume PhoneStage for now. The tablet version of the right-edge
71+ // tutorial is still being spec'd by the design team.
72+ PhoneStage {
73+ id: stage
74+ objectName: "stage"
75+ anchors.top: parent.top
76+ width: parent.width
77+ height: parent.height
78+ applicationManager: fakeAppManager
79+ color: "transparent"
80+ interactive: false
81+ altTabEnabled: false
82+ focusFirstApp: false
83+ startScale: 0.8
84+ endScale: 0.6
85+ dragAreaOverlap: units.gu(2)
86+
87+ onOpened: {
88+ overlay.show();
89+ root.textOpacity = 0;
90+ slider.visible = false;
91+ }
92+
93+ onDraggingChanged: {
94+ if (!dragging) {
95+ if (!overlay.shown) {
96+ root.showError();
97+ }
98+ teaseAnimation.complete();
99+ }
100+ }
101+ },
102+
103+ Showable {
104+ id: overlay
105+ objectName: "overlay"
106+ anchors.fill: parent
107+
108+ opacity: 0
109+ shown: false
110+ showAnimation: UbuntuNumberAnimation { property: "opacity"; to: 1 }
111+
112+ Label {
113+ anchors.top: parent.top
114+ anchors.topMargin: root.panel.panelHeight + units.gu(2)
115+ anchors.left: parent.left
116+ anchors.leftMargin: units.gu(2)
117+ anchors.right: parent.right
118+ anchors.rightMargin: units.gu(2)
119+ wrapMode: Text.Wrap
120+ horizontalAlignment: Text.AlignHCenter
121+ fontSize: "large"
122+ text: i18n.tr("View all your running tasks.")
123+ }
124+
125+ LocalComponents.Tick {
126+ objectName: "tick"
127+ anchors.bottom: bottomOverlayText.top
128+ anchors.bottomMargin: units.gu(1)
129+ anchors.horizontalCenter: bottomOverlayText.horizontalCenter
130+ onClicked: root.hide()
131+ }
132+
133+ Label {
134+ id: bottomOverlayText
135+ anchors.bottom: parent.bottom
136+ anchors.bottomMargin: units.gu(2)
137+ anchors.left: parent.left
138+ anchors.leftMargin: units.gu(2)
139+ anchors.right: parent.right
140+ anchors.rightMargin: units.gu(2)
141+ wrapMode: Text.Wrap
142+ horizontalAlignment: Text.AlignHCenter
143+ fontSize: "small"
144+ text: i18n.tr("Tap here to continue.")
145+ }
146+ }
147+ ]
148+ }
149+
150+ ListModel {
151+ id: fakeAppManager
152+
153+ readonly property string focusedApplicationId: "facebook"
154+
155+ function focusApplication(appId) {}
156+ function requestFocusApplication(appId) {}
157+ function findApplication(appId) {return null;}
158+
159+ signal applicationAdded(string appId)
160+ signal applicationRemoved(string appId)
161+ signal focusRequested(string appId)
162+
163+ ListElement {
164+ appId: "facebook"
165+ fullscreen: false
166+ name: ""
167+ icon: ""
168+ state: ApplicationInfoInterface.Stopped
169+ splashTitle: ""
170+ splashImage: ""
171+ splashShowHeader: false
172+ splashColor: "transparent"
173+ splashColorHeader: "transparent"
174+ splashColorFooter: "transparent"
175+ defaultScreenshot: "../Tutorial/graphics/facebook.png"
176+ }
177+
178+ ListElement {
179+ appId: "camera"
180+ fullscreen: false
181+ name: ""
182+ icon: ""
183+ state: ApplicationInfoInterface.Stopped
184+ splashTitle: ""
185+ splashImage: ""
186+ splashShowHeader: false
187+ splashColor: "transparent"
188+ splashColorHeader: "transparent"
189+ splashColorFooter: "transparent"
190+ defaultScreenshot: "../Tutorial/graphics/camera.png"
191+ }
192+
193+ ListElement {
194+ appId: "gallery"
195+ fullscreen: false
196+ name: ""
197+ icon: ""
198+ state: ApplicationInfoInterface.Stopped
199+ splashTitle: ""
200+ splashImage: ""
201+ splashShowHeader: false
202+ splashColor: "transparent"
203+ splashColorHeader: "transparent"
204+ splashColorFooter: "transparent"
205+ defaultScreenshot: "../Tutorial/graphics/gallery.png"
206+ }
207+
208+ ListElement {
209+ appId: "dialer"
210+ fullscreen: false
211+ name: ""
212+ icon: ""
213+ state: ApplicationInfoInterface.Stopped
214+ splashTitle: ""
215+ splashImage: ""
216+ splashShowHeader: false
217+ splashColor: "transparent"
218+ splashColorHeader: "transparent"
219+ splashColorFooter: "transparent"
220+ defaultScreenshot: "../Tutorial/graphics/dialer.png"
221+ }
222+ }
223+}
qml/Tutorial/graphics/camera.pngnew file

Binary file not shown.

qml/Tutorial/graphics/chevron.pngnew file

Binary file not shown.

qml/Tutorial/graphics/dialer.pngnew file

Binary file not shown.

qml/Tutorial/graphics/facebook.pngnew file

Binary file not shown.

qml/Tutorial/graphics/gallery.pngnew file

Binary file not shown.

qml/Tutorial/graphics/tick.pngnew file

Binary file not shown.

tests/autopilot/unity8/shell/emulators/edges_demo.pydeleted−140
@@ -1,140 +0,0 @@
1−# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2−#
3−# Unity Autopilot Test Suite
4−# Copyright (C) 2014 Canonical
5−#
6−# This program is free software: you can redistribute it and/or modify
7−# it under the terms of the GNU General Public License as published by
8−# the Free Software Foundation, either version 3 of the License, or
9−# (at your option) any later version.
10−#
11−# This program is distributed in the hope that it will be useful,
12−# but WITHOUT ANY WARRANTY; without even the implied warranty of
13−# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14−# GNU General Public License for more details.
15−#
16−# You should have received a copy of the GNU General Public License
17−# along with this program. If not, see <http://www.gnu.org/licenses/>.
18−#
19−
20−import logging
21−import time
22−
23−import ubuntuuitoolkit
24−
25−import autopilot
26−from autopilot import introspection
27−
28−
29−logger = logging.getLogger(__name__)
30−
31−
32−class RightEdgeDemoOverlay(
33− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
34−
35− @classmethod
36− def validate_dbus_object(cls, path, state):
37− name = introspection.get_classname_from_path(path)
38− if name == b'EdgeDemoOverlay':
39− if state['edge'][1] == 'right':
40− return True
41− return False
42−
43− @autopilot.logging.log_action(logger.info)
44− def swipe(self):
45− """Swipe to the left to complete this demo step."""
46− x, y, width, height = self.globalRect
47− start_x = x + width
48− stop_x = x
49− start_y = stop_y = y + height // 2
50− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
51− return self.get_root_instance().wait_select_single(
52− edge='top', active=True)
53−
54−
55−class TopEdgeDemoOverlay(
56− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
57−
58− @classmethod
59− def validate_dbus_object(cls, path, state):
60− name = introspection.get_classname_from_path(path)
61− if name == b'EdgeDemoOverlay':
62− if state['edge'][1] == 'top':
63− return True
64− return False
65−
66− @autopilot.logging.log_action(logger.info)
67− def swipe(self):
68− """Swipe to the bottom to complete this demo step."""
69− x, y, width, height = self.globalRect
70− start_x = stop_x = x + width // 2
71− start_y = y
72− stop_y = y + height
73− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
74− return self.get_root_instance().wait_select_single(
75− edge='bottom', active=True)
76−
77−
78−class BottomEdgeDemoOverlay(
79− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
80−
81− @classmethod
82− def validate_dbus_object(cls, path, state):
83− name = introspection.get_classname_from_path(path)
84− if name == b'EdgeDemoOverlay':
85− if state['edge'][1] == 'bottom':
86− return True
87− return False
88−
89− @autopilot.logging.log_action(logger.info)
90− def swipe(self):
91− """Swipe to the top to complete this demo step."""
92− x, y, width, height = self.globalRect
93− start_x = stop_x = x + width // 2
94− start_y = y + height
95− stop_y = y
96− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
97− return self.get_root_instance().wait_select_single(
98− edge='left', active=True)
99−
100−
101−class LeftEdgeDemoOverlay(
102− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
103−
104− @classmethod
105− def validate_dbus_object(cls, path, state):
106− name = introspection.get_classname_from_path(path)
107− if name == b'EdgeDemoOverlay':
108− if state['edge'][1] == 'left':
109− return True
110− return False
111−
112− @autopilot.logging.log_action(logger.info)
113− def swipe(self):
114− """Swipe to the right to complete this demo step."""
115− x, y, width, height = self.globalRect
116− start_x = x
117− stop_x = x + width
118− start_y = stop_y = y + height // 2
119− self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
120− return self.get_root_instance().wait_select_single(
121− edge='none', active=True)
122−
123−
124−class FinalEdgeDemoOverlay(
125− ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
126−
127− @classmethod
128− def validate_dbus_object(cls, path, state):
129− name = introspection.get_classname_from_path(path)
130− if name == b'EdgeDemoOverlay':
131− if state['edge'][1] == 'none':
132− return True
133− return False
134−
135− @autopilot.logging.log_action(logger.info)
136− def tap_to_start(self):
137− """Tap to finish the demo and start using the Ubuntu Touch."""
138− time.sleep(1)
139− self.pointing_device.click_object(self)
140− self.shown.wait_for(False)
tests/autopilot/unity8/shell/emulators/tutorial.pynew file+74
@@ -0,0 +1,74 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import logging
21+import time
22+
23+import ubuntuuitoolkit
24+
25+import autopilot
26+from autopilot import introspection
27+
28+
29+logger = logging.getLogger(__name__)
30+
31+
32+class TutorialPage(
33+ ubuntuuitoolkit.UbuntuUIToolkitCustomProxyObjectBase):
34+
35+ @classmethod
36+ def validate_dbus_object(cls, path, state):
37+ name = introspection.get_classname_from_path(path)
38+ return name in (b'TutorialPage', b'TutorialLeft',
39+ b'TutorialLeftFinish', b'TutorialRight',
40+ b'TutorialBottom', b'TutorialBottomFinish')
41+
42+ @autopilot.logging.log_action(logger.info)
43+ def short_swipe_right(self):
44+ self.shown.wait_for(True)
45+ x, y, width, height = self.globalRect
46+ start_x = x
47+ stop_x = x + width // 3
48+ start_y = stop_y = y + height // 2
49+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
50+
51+ @autopilot.logging.log_action(logger.info)
52+ def swipe_left(self):
53+ self.shown.wait_for(True)
54+ x, y, width, height = self.globalRect
55+ start_x = width
56+ stop_x = x
57+ start_y = stop_y = y + height // 2
58+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
59+
60+ @autopilot.logging.log_action(logger.info)
61+ def swipe_up(self):
62+ self.shown.wait_for(True)
63+ x, y, width, height = self.globalRect
64+ start_y = height
65+ stop_y = y
66+ start_x = stop_x = x + width // 2
67+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
68+
69+ @autopilot.logging.log_action(logger.info)
70+ def tap(self):
71+ """Tap the tick button to complete this step."""
72+ self.shown.wait_for(True)
73+ button = self.wait_select_single(objectName="tick")
74+ self.pointing_device.click_object(button)
tests/autopilot/unity8/shell/fixture_setup.py+6−6
@@ -54,7 +54,7 @@ class FakeScopes(fixtures.Fixture):
5454 return ld_library_path
5555
5656
57−class EdgesDemo(fixtures.Fixture):
57+class Tutorial(fixtures.Fixture):
5858
5959 def __init__(self, enable):
6060 super().__init__()
@@ -62,12 +62,12 @@ class EdgesDemo(fixtures.Fixture):
6262
6363 def setUp(self):
6464 super().setUp()
65− original_state = self._is_edges_demo_enabled()
65+ original_state = self._is_tutorial_enabled()
6666 if self.enable != original_state:
67− self.addCleanup(self._set_edges_demo, original_state)
68− self._set_edges_demo(self.enable)
67+ self.addCleanup(self._set_tutorial, original_state)
68+ self._set_tutorial(self.enable)
6969
70− def _is_edges_demo_enabled(self):
70+ def _is_tutorial_enabled(self):
7171 command = [
7272 'dbus-send', '--system', '--print-reply',
7373 '--dest=org.freedesktop.Accounts',
@@ -79,7 +79,7 @@ class EdgesDemo(fixtures.Fixture):
7979 output = subprocess.check_output(command, universal_newlines=True)
8080 return True if output.count('true') else False
8181
82− def _set_edges_demo(self, value):
82+ def _set_tutorial(self, value):
8383 value_string = 'true' if value else 'false'
8484 command = [
8585 'dbus-send', '--system', '--print-reply',
tests/autopilot/unity8/shell/tests/test_edges_demo.py → tests/autopilot/unity8/shell/tests/test_tutorial.pyrenamed+22−16
@@ -24,29 +24,35 @@ from unity8.shell import (
2424 fixture_setup,
2525 tests
2626)
27−# unused import to load the edge emulators custom proxy objects.
28−from unity8.shell.emulators import edges_demo # NOQA
27+# unused import to load the tutorial emulators custom proxy objects.
28+from unity8.shell.emulators import tutorial # NOQA
2929
3030
31−class EdgesDemoTestCase(tests.UnityTestCase):
31+class TutorialTestCase(tests.UnityTestCase):
3232
3333 def setUp(self):
34− super(EdgesDemoTestCase, self).setUp()
34+ super(TutorialTestCase, self).setUp()
3535 self._qml_mock_enabled = False
3636 self._data_dirs_mock_enabled = False
3737 self._lightdm_mock_type = False
3838
39− self.useFixture(fixture_setup.EdgesDemo(True))
39+ self.useFixture(fixture_setup.Tutorial(True))
4040 self.unity = self.launch_unity()
4141
42− def test_complete_edge_demo(self):
43− edge_demo = self.unity.select_single('EdgeDemo')
44− self.assertThat(edge_demo.running, Eventually(Equals(True)))
45− right_edge_overlay = self.unity.wait_select_single(
46− edge='right', active=True)
47− top_edge_overlay = right_edge_overlay.swipe()
48− bottom_edge_overlay = top_edge_overlay.swipe()
49− left_edge_overlay = bottom_edge_overlay.swipe()
50− final_overlay = left_edge_overlay.swipe()
51− final_overlay.tap_to_start()
52− self.assertThat(edge_demo.running, Eventually(Equals(False)))
42+ def test_complete_tutorial(self):
43+ greeter = self.main_window.get_greeter()
44+ tutorial = self.unity.select_single('Tutorial')
45+ self.assertThat(tutorial.running, Eventually(Equals(True)))
46+ greeter.swipe()
47+ page = self.unity.wait_select_single(objectName='tutorialLeft')
48+ page.short_swipe_right()
49+ page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
50+ page.tap()
51+ page = self.unity.wait_select_single(objectName='tutorialRight')
52+ page.swipe_left()
53+ page.tap()
54+ page = self.unity.wait_select_single(objectName='tutorialBottom')
55+ page.swipe_up()
56+ page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
57+ page.tap()
58+ self.assertThat(tutorial.running, Eventually(Equals(False)))
tests/qmltests/CMakeLists.txt+1−1
@@ -25,7 +25,6 @@ add_qml_test(Components Background)
2525add_qml_test(Components Carousel)
2626add_qml_test(Components Dialogs)
2727add_qml_test(Components DraggingArea)
28−add_qml_test(Components EdgeDemoOverlay)
2928add_qml_test(Components LazyImage)
3029add_qml_test(Components Lockscreen)
3130add_qml_test(Components Rating)
@@ -91,4 +90,5 @@ add_qml_test(Stages SurfaceContainer ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY
9190add_qml_test(Stages SessionContainer ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/libusermetrics:${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single")
9291add_qml_test(Stages WindowMoveResizeArea)
9392add_qml_test(Stages Splash)
93+add_qml_test(Tutorial Tutorial ENVIRONMENT "LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}/tests/mocks/LightDM/single-pin:${CMAKE_BINARY_DIR}/tests/mocks/QMenuModel")
9494add_qml_test(Wizard Wizard)
tests/qmltests/Components/tst_EdgeDemoOverlay.qmldeleted−123
@@ -1,123 +0,0 @@
1−/*
2− * Copyright 2013 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU General Public License as published by
6− * the Free Software Foundation; version 3.
7− *
8− * This program is distributed in the hope that it will be useful,
9− * but WITHOUT ANY WARRANTY; without even the implied warranty of
10− * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11− * GNU General Public License for more details.
12− *
13− * You should have received a copy of the GNU General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.0
18−import QtTest 1.0
19−import Unity.Test 0.1 as UT
20−import "../../../qml/Components"
21−
22−Item {
23− id: root
24− width: boxWidth * 3
25− height: boxHeight * 2
26−
27− property int boxWidth: 250
28− property int boxHeight: 250
29−
30− EdgeDemoOverlay {
31− id: top
32− edge: "top"
33− title: "Top"
34− text: "Displayed on top left"
35− anchors.left: parent.left
36− anchors.top: parent.top
37− width: boxWidth
38− height: boxHeight
39− }
40−
41− EdgeDemoOverlay {
42− id: right
43− edge: "right"
44− title: "Right"
45− text: "Displayed on top right"
46− anchors.right: parent.right
47− anchors.top: parent.top
48− width: boxWidth
49− height: boxHeight
50− }
51−
52− EdgeDemoOverlay {
53− id: left
54− edge: "left"
55− title: "Left"
56− text: "Displayed on bottom right"
57− anchors.right: parent.right
58− anchors.bottom: parent.bottom
59− width: boxWidth
60− height: boxHeight
61− available: false
62− }
63−
64− EdgeDemoOverlay {
65− id: bottom
66− edge: "bottom"
67− title: "Bottom"
68− text: "Displayed on bottom left"
69− anchors.left: parent.left
70− anchors.bottom: parent.bottom
71− width: boxWidth
72− height: boxHeight
73− }
74−
75− EdgeDemoOverlay {
76− id: none
77− edge: "none"
78− title: "None"
79− text: "Displayed on top middle"
80− anchors.horizontalCenter: parent.horizontalCenter
81− anchors.top: parent.top
82− width: boxWidth
83− height: boxHeight
84− }
85−
86− SignalSpy {
87− id: signalSpy
88− }
89−
90− UT.UnityTestCase {
91− name: "EdgeDemoOverlay"
92− when: windowShown
93−
94− function test_animations() {
95− compare(right.running, true)
96−
97− compare(left.running, false)
98− left.available = true
99− compare(left.running, true)
100− }
101−
102− function test_skip() {
103− signalSpy.target = bottom
104− signalSpy.signalName = "skip"
105− signalSpy.clear()
106− var bottomSkip = findChild(bottom, "skipLabel")
107− mousePress(bottomSkip, 1, 1)
108− mouseRelease(bottomSkip, 1, 1)
109− signalSpy.wait()
110− compare(bottom.available, false)
111−
112− // Test that the 'none' edge skips anywhere
113− signalSpy.target = none
114− signalSpy.clear()
115− var backgroundShade = findChild(none, "backgroundShadeMouseArea")
116− tryCompare(backgroundShade, "enabled", true)
117− mousePress(backgroundShade, 1, 1)
118− mouseRelease(backgroundShade, 1, 1)
119− signalSpy.wait()
120− compare(none.available, false)
121− }
122− }
123−}
tests/qmltests/Tutorial/tst_Tutorial.qmlnew file+341
@@ -0,0 +1,341 @@
1+/*
2+ * Copyright (C) 2013,2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import AccountsService 0.1
20+import LightDM 0.1 as LightDM
21+import Ubuntu.Components 1.1
22+import Unity.Application 0.1
23+import Unity.Test 0.1 as UT
24+
25+import "../../../qml"
26+
27+Item {
28+ id: root
29+ width: shellLoader.width + buttons.width
30+ height: shellLoader.height
31+
32+ QtObject {
33+ id: applicationArguments
34+
35+ function hasGeometry() {
36+ return false;
37+ }
38+
39+ function width() {
40+ return 0;
41+ }
42+
43+ function height() {
44+ return 0;
45+ }
46+ }
47+
48+ Row {
49+ spacing: 0
50+ anchors.fill: parent
51+
52+ Loader {
53+ id: shellLoader
54+
55+ width: units.gu(40)
56+ height: units.gu(71)
57+
58+ property bool itemDestroyed: false
59+ sourceComponent: Component {
60+ Shell {
61+ property string indicatorProfile: "phone"
62+
63+ Component.onDestruction: {
64+ shellLoader.itemDestroyed = true;
65+ }
66+ }
67+ }
68+ }
69+
70+ Rectangle {
71+ id: buttons
72+ color: "white"
73+ width: units.gu(30)
74+ height: shellLoader.height
75+
76+ Column {
77+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
78+ spacing: units.gu(1)
79+ Row {
80+ anchors { left: parent.left; right: parent.right }
81+ Button {
82+ text: "Restart Tutorial"
83+ onClicked: {
84+ if (shellLoader.status !== Loader.Ready)
85+ return;
86+
87+ AccountsService.demoEdges = false;
88+ AccountsService.demoEdges = true;
89+ }
90+ }
91+ }
92+ }
93+ }
94+ }
95+
96+ UT.UnityTestCase {
97+ id: testCase
98+ name: "Tutorial"
99+ when: windowShown
100+
101+ property Item shell: shellLoader.status === Loader.Ready ? shellLoader.item : null
102+ property real halfWidth: shell ? shell.width / 2 : 0
103+ property real halfHeight: shell ? shell.height / 2 : 0
104+
105+ function init() {
106+ tryCompare(shell, "enabled", true); // enabled by greeter when ready
107+ AccountsService.demoEdges = false;
108+ AccountsService.demoEdges = true;
109+ swipeAwayGreeter();
110+ }
111+
112+ function cleanup() {
113+ shellLoader.itemDestroyed = false;
114+
115+ shellLoader.active = false;
116+
117+ tryCompare(shellLoader, "status", Loader.Null);
118+ tryCompare(shellLoader, "item", null);
119+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
120+ // item might still be alive. So if we set Loader.active back to true
121+ // again right now we will get the very same Shell instance back. So no reload
122+ // actually took place. Likely because Loader waits until the next event loop
123+ // iteration to do its work. So to ensure the reload, we will wait until the
124+ // Shell instance gets destroyed.
125+ tryCompare(shellLoader, "itemDestroyed", true);
126+
127+ // kill all (fake) running apps
128+ killApps();
129+
130+ // reload our test subject to get it in a fresh state once again
131+ shellLoader.active = true;
132+
133+ tryCompare(shellLoader, "status", Loader.Ready);
134+ removeTimeConstraintsFromDirectionalDragAreas(shellLoader.item);
135+ }
136+
137+ function killApps() {
138+ while (ApplicationManager.count > 1) {
139+ var appIndex = ApplicationManager.get(0).appId == "unity8-dash" ? 1 : 0
140+ ApplicationManager.stopApplication(ApplicationManager.get(appIndex).appId);
141+ }
142+ compare(ApplicationManager.count, 1)
143+ }
144+
145+ function swipeAwayGreeter() {
146+ var coverPage = findChild(shell, "coverPage");
147+ tryCompare(coverPage, "showProgress", 1);
148+
149+ touchFlick(shell, halfWidth, halfHeight, shell.width, halfHeight);
150+
151+ // wait until the animation has finished
152+ var greeter = findChild(shell, "greeter");
153+ tryCompare(greeter, "required", false);
154+ waitForRendering(greeter);
155+ }
156+
157+ function waitForPage(name) {
158+ waitForRendering(findChild(shell, name));
159+ var page = findChild(shell, name);
160+ tryCompare(page, "shown", true);
161+ tryCompare(page.showAnimation, "running", false);
162+ return page;
163+ }
164+
165+ function checkTopEdge() {
166+ touchFlick(shell, halfWidth, 0, halfWidth, halfHeight);
167+
168+ var panel = findChild(shell, "panel");
169+ tryCompare(panel.indicators, "fullyClosed", true);
170+ }
171+
172+ function checkLeftEdge() {
173+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
174+
175+ var launcher = findChild(shell, "launcher");
176+ tryCompare(launcher, "state", "");
177+ }
178+
179+ function checkRightEdge() {
180+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
181+
182+ var stage = findChild(shell, "stage");
183+ var spreadView = findChild(stage, "spreadView");
184+ tryCompare(spreadView, "phase", 0);
185+ }
186+
187+ function checkBottomEdge() {
188+ // Can't actually check effect of swipe, since dash isn't really loaded
189+ var applicationsDisplayLoader = findChild(shell, "applicationsDisplayLoader");
190+ tryCompare(applicationsDisplayLoader.item, "interactive", false);
191+ }
192+
193+ function checkFinished() {
194+ tryCompare(AccountsService, "demoEdges", false);
195+
196+ var tutorial = findChild(shell, "tutorial");
197+ tryCompare(tutorial, "running", false);
198+
199+ var launcher = findChild(shell, "launcher");
200+ tryCompare(launcher, "shown", false);
201+ }
202+
203+ function goToPage(name) {
204+ var page = waitForPage("tutorialLeft");
205+ checkTopEdge();
206+ checkRightEdge();
207+ checkBottomEdge();
208+ if (name === "tutorialLeft") return page;
209+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight);
210+
211+ page = waitForPage("tutorialLeftFinish");
212+ if (name === "tutorialLeftFinish") return page;
213+ var tick = findChild(page, "tick");
214+ tap(tick);
215+
216+ page = waitForPage("tutorialRight");
217+ checkTopEdge();
218+ checkLeftEdge();
219+ checkBottomEdge();
220+ if (name === "tutorialRight") return page;
221+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
222+ var overlay = findChild(page, "overlay");
223+ tryCompare(overlay, "shown", true);
224+ var tick = findChild(page, "tick");
225+ tap(tick);
226+
227+ var page = waitForPage("tutorialBottom");
228+ checkTopEdge();
229+ checkLeftEdge();
230+ checkRightEdge();
231+ if (name === "tutorialBottom") return page;
232+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
233+
234+ var page = waitForPage("tutorialBottomFinish");
235+ checkTopEdge();
236+ checkLeftEdge();
237+ checkRightEdge();
238+ checkBottomEdge();
239+ if (name === "tutorialBottomFinish") return page;
240+ var tick = findChild(page, "tick");
241+ tap(tick);
242+
243+ checkFinished();
244+ return null;
245+ }
246+
247+ function test_walkthrough() {
248+ goToPage(null);
249+ }
250+
251+ function test_launcherShortDrag() {
252+ // goToPage does a normal launcher pull. But here we want to test
253+ // just barely pulling the launcher out and letting go (i.e. not
254+ // triggering the "progress" property of Launcher).
255+
256+ var left = goToPage("tutorialLeft");
257+
258+ // Make sure we don't do anything if we don't pull the launcher
259+ // out much.
260+ var launcher = findChild(shell, "launcher");
261+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.4, halfHeight);
262+ tryCompare(launcher, "state", ""); // should remain hidden
263+ tryCompare(left, "shown", true); // and we should still be on left
264+
265+ // Now drag out but not past launcher itself
266+ touchFlick(shell, 0, halfHeight, launcher.panelWidth * 0.9, halfHeight);
267+
268+ waitForPage("tutorialLeftFinish");
269+ }
270+
271+ function test_launcherLongDrag() {
272+ // goToPage does a normal launcher pull. But here we want to test
273+ // a full pull across the page.
274+
275+ var left = goToPage("tutorialLeft");
276+
277+ var launcher = findChild(shell, "launcher");
278+ touchFlick(shell, 0, halfHeight, shell.width, halfHeight);
279+
280+ var errorTextLabel = findChild(left, "errorTextLabel");
281+ var errorTitleLabel = findChild(left, "errorTitleLabel");
282+ tryCompare(launcher, "state", ""); // launcher goes away
283+ tryCompare(left, "shown", true); // still on left page
284+ tryCompare(errorTextLabel, "opacity", 1); // show error
285+ tryCompare(errorTitleLabel, "opacity", 1); // show error
286+ }
287+
288+ function test_launcherDragBack() {
289+ // goToPage does a full launcher pull. But here we test pulling
290+ // all the way out, then dragging back into place.
291+
292+ var left = goToPage("tutorialLeft");
293+ touchFlick(shell, 0, halfHeight, halfWidth, halfHeight, true, false);
294+ touchFlick(shell, halfWidth, halfHeight, 0, halfHeight, false, true);
295+
296+ tryCompare(left, "shown", true); // and we should still be on left
297+ }
298+
299+ function test_spread() {
300+ // Unfortunately, most of what we want to test of the spread is
301+ // "did it render correctly?" but that's hard to test. So instead,
302+ // just poke and prod it a little bit to see if some of the values
303+ // we'd expect to be correct, are so.
304+
305+ var right = goToPage("tutorialRight");
306+ var stage = findChild(right, "stage");
307+ var delegate0 = findChild(right, "appDelegate0");
308+
309+ tryCompare(stage, "dragProgress", 0);
310+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
311+ verify(stage.dragProgress > 0);
312+ compare(stage.dragProgress, -delegate0.xTranslate);
313+ touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
314+ tryCompare(stage, "dragProgress", 0);
315+
316+ tryCompare(delegate0, "x", shell.width);
317+
318+ var screenshotImage = findChild(right, "screenshotImage");
319+ tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
320+ tryCompare(screenshotImage, "visible", true);
321+ }
322+
323+ function test_bottomShortDrag() {
324+ var bottom = goToPage("tutorialBottom");
325+
326+ touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
327+
328+ var errorTextLabel = findChild(bottom, "errorTextLabel");
329+ var errorTitleLabel = findChild(bottom, "errorTitleLabel");
330+ tryCompare(bottom, "shown", true); // still on bottom page
331+ tryCompare(errorTextLabel, "opacity", 1); // show error
332+ tryCompare(errorTitleLabel, "opacity", 1); // show error
333+ }
334+
335+ function test_interrupted() {
336+ goToPage("tutorialLeft");
337+ ApplicationManager.startApplication("dialer-app");
338+ checkFinished();
339+ }
340+ }
341+}
tests/qmltests/tst_TabletShell.qml+2−2
@@ -282,8 +282,8 @@ Item {
282282 selectUser(data.user)
283283
284284 AccountsService.demoEdges = data.demo
285− var edgeDemo = findChild(shell, "edgeDemo")
286− tryCompare(edgeDemo, "running", data.demo)
285+ var tutorial = findChild(shell, "tutorial");
286+ tryCompare(tutorial, "running", data.demo);
287287
288288 swipeFromLeftEdge(shell.width * 0.75)
289289 wait(500) // to give time to handle dash() signal from Launcher