on xenial help sign in
Add new right-edge and bottom-edge screens to the first-boot edge tutorial Fixes: #1383297
d71e55bMichael Terry
qml/Shell.qml+7−3
@@ -290,7 +290,7 @@ Item {
290 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
290 source: usageModeSettings.usageMode === "Windowed" ? "Stages/DesktopStage.qml"
291 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
291 : tabletMode ? "Stages/TabletStage.qml" : "Stages/PhoneStage.qml"
292
292
293− property bool interactive: tutorial.stagesEnabled
293+ property bool interactive: tutorial.spreadEnabled
294 && !greeter.shown
294 && !greeter.shown
295 && !lockscreen.shown
295 && !lockscreen.shown
296 && panel.indicators.fullyClosed
296 && panel.indicators.fullyClosed
@@ -323,7 +323,7 @@ Item {
323 Binding {
323 Binding {
324 target: applicationsDisplayLoader.item
324 target: applicationsDisplayLoader.item
325 property: "spreadEnabled"
325 property: "spreadEnabled"
326− value: tutorial.stagesEnabled && !greeter.hasLockedApp
326+ value: tutorial.spreadEnabled && !greeter.hasLockedApp
327 }
327 }
328 Binding {
328 Binding {
329 target: applicationsDisplayLoader.item
329 target: applicationsDisplayLoader.item
@@ -889,8 +889,12 @@ Item {
889 panel: panel
889 panel: panel
890 stages: stages
890 stages: stages
891 overlay: overlay
891 overlay: overlay
892+ edgeSize: shell.edgeSize
892
893
894+ onFinished: {
893− onFinished: AccountsService.demoEdges = false
895+ AccountsService.demoEdges = false;
896+ active = false; // for immediate response / if AS is having problems
897+ }
894 }
898 }
895
899
896 Connections {
900 Connections {
qml/Stages/ApplicationWindow.qml+14−1
@@ -43,6 +43,7 @@ FocusScope {
43 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
43 readonly property color splashColor: root.application ? root.application.splashColor : "#00000000"
44 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
44 readonly property color splashColorHeader: root.application ? root.application.splashColorHeader : "#00000000"
45 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
45 readonly property color splashColorFooter: root.application ? root.application.splashColorFooter : "#00000000"
46+ readonly property url defaultScreenshot: root.application ? root.application.defaultScreenshot : ""
46
47
47 // Whether the Application had a surface before but lost it.
48 // Whether the Application had a surface before but lost it.
48 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
49 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
@@ -72,7 +73,7 @@ FocusScope {
72 Image {
73 Image {
73 id: screenshotImage
74 id: screenshotImage
74 objectName: "screenshotImage"
75 objectName: "screenshotImage"
75− source: ""
76+ source: d.defaultScreenshot
76 anchors.fill: parent
77 anchors.fill: parent
77 antialiasing: !root.interactive
78 antialiasing: !root.interactive
78
79
@@ -125,6 +126,7 @@ FocusScope {
125 }
126 }
126
127
127 StateGroup {
128 StateGroup {
129+ id: stateGroup
128 objectName: "applicationWindowStateGroup"
130 objectName: "applicationWindowStateGroup"
129 states: [
131 states: [
130 State {
132 State {
@@ -217,6 +219,17 @@ FocusScope {
217 PropertyAction { target: screenshotImage; property: "source"; value: "" }
219 PropertyAction { target: screenshotImage; property: "source"; value: "" }
218 }
220 }
219 },
221 },
222+ Transition {
223+ from: "splashScreen"; to: "screenshot"
224+ SequentialAnimation {
225+ PropertyAction { target: screenshotImage
226+ property: "visible"; value: true }
227+ UbuntuNumberAnimation { target: screenshotImage; property: "opacity";
228+ from: 0.0; to: 1.0
229+ duration: UbuntuAnimation.BriskDuration }
230+ PropertyAction { target: splashLoader; property: "active"; value: false }
231+ }
232+ },
220 Transition {
233 Transition {
221 from: "surface"; to: "void"
234 from: "surface"; to: "void"
222 SequentialAnimation {
235 SequentialAnimation {
qml/Stages/PhoneStage.qml+62−34
@@ -32,6 +32,21 @@ Rectangle {
32 property bool spreadEnabled: true // If false, animations and right edge will be disabled
32 property bool spreadEnabled: true // If false, animations and right edge will be disabled
33 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
33 property real inverseProgress: 0 // This is the progress for left edge drags, in pixels.
34 property int orientation: Qt.PortraitOrientation
34 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()
35
50
36 color: "#111111"
51 color: "#111111"
37
52
@@ -52,30 +67,30 @@ Rectangle {
52 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
67 if (inverseProgress == 0 && priv.oldInverseProgress > 0) {
53 // left edge drag released. Minimum distance is given by design.
68 // left edge drag released. Minimum distance is given by design.
54 if (priv.oldInverseProgress > units.gu(22)) {
69 if (priv.oldInverseProgress > units.gu(22)) {
55− ApplicationManager.requestFocusApplication("unity8-dash");
70+ applicationManager.requestFocusApplication("unity8-dash");
56 }
71 }
57 }
72 }
58 priv.oldInverseProgress = inverseProgress;
73 priv.oldInverseProgress = inverseProgress;
59 }
74 }
60
75
61 Connections {
76 Connections {
62− target: ApplicationManager
77+ target: applicationManager
63
78
64 onFocusRequested: {
79 onFocusRequested: {
65 if (spreadView.phase > 0) {
80 if (spreadView.phase > 0) {
66 spreadView.snapTo(priv.indexOf(appId));
81 spreadView.snapTo(priv.indexOf(appId));
67 } else {
82 } else {
68− ApplicationManager.focusApplication(appId);
83+ applicationManager.focusApplication(appId);
69 }
84 }
70 }
85 }
71
86
72 onApplicationAdded: {
87 onApplicationAdded: {
73 if (spreadView.phase == 2) {
88 if (spreadView.phase == 2) {
74− spreadView.snapTo(ApplicationManager.count - 1);
89+ spreadView.snapTo(applicationManager.count - 1);
75 } else {
90 } else {
76 spreadView.phase = 0;
91 spreadView.phase = 0;
77 spreadView.contentX = -spreadView.shift;
92 spreadView.contentX = -spreadView.shift;
78− ApplicationManager.focusApplication(appId);
93+ applicationManager.focusApplication(appId);
79 }
94 }
80 }
95 }
81
96
@@ -90,9 +105,9 @@ Rectangle {
90 }
105 }
91
106
92 function focusTopMostApp() {
107 function focusTopMostApp() {
93− if (ApplicationManager.count > 0) {
108+ if (applicationManager.count > 0) {
94− var topmostApp = ApplicationManager.get(0);
109+ var topmostApp = applicationManager.get(0);
95− ApplicationManager.focusApplication(topmostApp.appId);
110+ applicationManager.focusApplication(topmostApp.appId);
96 }
111 }
97 }
112 }
98 }
113 }
@@ -100,20 +115,21 @@ Rectangle {
100 QtObject {
115 QtObject {
101 id: priv
116 id: priv
102
117
118+ readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
103− property string focusedAppId: ApplicationManager.focusedApplicationId
119+ property string focusedAppId: applicationManager.focusedApplicationId
104− property var focusedApplication: ApplicationManager.findApplication(focusedAppId)
120+ property var focusedApplication: applicationManager.findApplication(focusedAppId)
105 property var focusedAppDelegate: null
121 property var focusedAppDelegate: null
106
122
107 property real oldInverseProgress: 0
123 property real oldInverseProgress: 0
108− property bool animateX: true
124+ property bool animateX: false
109
125
110 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
126 onFocusedAppIdChanged: focusedAppDelegate = spreadRepeater.itemAt(0);
111
127
112 onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
128 onFocusedAppDelegateChanged: focusedAppDelegate.focus = true;
113
129
114 function indexOf(appId) {
130 function indexOf(appId) {
115− for (var i = 0; i < ApplicationManager.count; i++) {
131+ for (var i = 0; i < applicationManager.count; i++) {
116− if (ApplicationManager.get(i).appId == appId) {
132+ if (applicationManager.get(i).appId == appId) {
117 return i;
133 return i;
118 }
134 }
119 }
135 }
@@ -133,7 +149,7 @@ Rectangle {
133
149
134 // This indicates when the spreadView is active. That means, all the animations
150 // This indicates when the spreadView is active. That means, all the animations
135 // are activated and tiles need to line up for the spread.
151 // are activated and tiles need to line up for the spread.
136− readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized
152+ readonly property bool active: shiftedContentX > 0 || spreadDragArea.status === DirectionalDragArea.Recognized || !root.focusFirstApp
137
153
138 // The flickable needs to fill the screen in order to get touch events all over.
154 // The flickable needs to fill the screen in order to get touch events all over.
139 // However, we don't want to the user to be able to scroll back all the way. For
155 // However, we don't want to the user to be able to scroll back all the way. For
@@ -199,10 +215,17 @@ Rectangle {
199 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
215 // Add 1 pixel to make sure we definitely hit positionMarker4 even with rounding errors of the animation.
200 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
216 snapAnimation.targetContentX = width * positionMarker4 + 1 - shift;
201 snapAnimation.start();
217 snapAnimation.start();
218+ root.opened();
202 }
219 }
203 }
220 }
204 function snapTo(index) {
221 function snapTo(index) {
205− if (ApplicationManager.count <= index) {
222+ if (!root.altTabEnabled) {
223+ // Reset to start instead
224+ snapAnimation.targetContentX = -shift;
225+ snapAnimation.start();
226+ return;
227+ }
228+ if (applicationManager.count <= index) {
206 // In case we're trying to snap to some non existing app, lets snap back to the first one
229 // In case we're trying to snap to some non existing app, lets snap back to the first one
207 index = 0;
230 index = 0;
208 }
231 }
@@ -217,10 +240,12 @@ Rectangle {
217 snapAnimation.start();
240 snapAnimation.start();
218 }
241 }
219
242
220− // In case the ApplicationManager already holds an app when starting up we're missing animations
243+ // In case the applicationManager already holds an app when starting up we're missing animations
221 // Make sure we end up in the same state
244 // Make sure we end up in the same state
222 Component.onCompleted: {
245 Component.onCompleted: {
223 spreadView.contentX = -spreadView.shift
246 spreadView.contentX = -spreadView.shift
247+ priv.animateX = true;
248+ snapAnimation.complete();
224 }
249 }
225
250
226 SequentialAnimation {
251 SequentialAnimation {
@@ -237,7 +262,7 @@ Rectangle {
237 ScriptAction {
262 ScriptAction {
238 script: {
263 script: {
239 if (spreadView.selectedIndex >= 0) {
264 if (spreadView.selectedIndex >= 0) {
240− ApplicationManager.focusApplication(ApplicationManager.get(spreadView.selectedIndex).appId);
265+ applicationManager.focusApplication(applicationManager.get(spreadView.selectedIndex).appId);
241
266
242 spreadView.selectedIndex = -1;
267 spreadView.selectedIndex = -1;
243 spreadView.phase = 0;
268 spreadView.phase = 0;
@@ -252,7 +277,7 @@ Rectangle {
252 // This width controls how much the spread can be flicked left/right. It's composed of:
277 // This width controls how much the spread can be flicked left/right. It's composed of:
253 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
278 // tileDistance * app count (with a minimum of 3 apps, in order to also allow moving 1 and 2 apps a bit)
254 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
279 // + some constant value (still scales with the screen width) which looks good and somewhat fills the screen
255− width: Math.max(3, ApplicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
280+ width: Math.max(3, applicationManager.count) * spreadView.tileDistance + (spreadView.width - spreadView.tileDistance) * 1.5
256 height: parent.height
281 height: parent.height
257 Behavior on width {
282 Behavior on width {
258 enabled: spreadView.closingIndex >= 0
283 enabled: spreadView.closingIndex >= 0
@@ -267,20 +292,22 @@ Rectangle {
267 x: spreadView.contentX
292 x: spreadView.contentX
268
293
269 onClicked: {
294 onClicked: {
295+ if (root.altTabEnabled) {
270− spreadView.snapTo(0);
296+ spreadView.snapTo(0);
297+ }
271 }
298 }
272
299
273 Repeater {
300 Repeater {
274 id: spreadRepeater
301 id: spreadRepeater
275 objectName: "spreadRepeater"
302 objectName: "spreadRepeater"
276− model: ApplicationManager
303+ model: applicationManager
277 delegate: TransformedSpreadDelegate {
304 delegate: TransformedSpreadDelegate {
278 id: appDelegate
305 id: appDelegate
279 objectName: "appDelegate" + index
306 objectName: "appDelegate" + index
280 startAngle: 45
307 startAngle: 45
281 endAngle: 5
308 endAngle: 5
282− startScale: 1.1
309+ startScale: root.startScale
283− endScale: 0.7
310+ endScale: root.endScale
284 startDistance: spreadView.tileDistance
311 startDistance: spreadView.tileDistance
285 endDistance: units.gu(.5)
312 endDistance: units.gu(.5)
286 width: spreadView.width
313 width: spreadView.width
@@ -288,11 +315,12 @@ Rectangle {
288 selected: spreadView.selectedIndex == index
315 selected: spreadView.selectedIndex == index
289 otherSelected: spreadView.selectedIndex >= 0 && !selected
316 otherSelected: spreadView.selectedIndex >= 0 && !selected
290 interactive: !spreadView.interactive && spreadView.phase === 0
317 interactive: !spreadView.interactive && spreadView.phase === 0
291− && spreadView.shiftedContentX === 0 && root.interactive && index === 0
318+ && spreadView.shiftedContentX === 0 && root.interactive && isFocused
292− swipeToCloseEnabled: spreadView.interactive && !snapAnimation.running
319+ swipeToCloseEnabled: spreadView.interactive && root.interactive && !snapAnimation.running
293 maximizedAppTopMargin: root.maximizedAppTopMargin
320 maximizedAppTopMargin: root.maximizedAppTopMargin
294 dropShadow: spreadView.active ||
321 dropShadow: spreadView.active ||
295 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
322 (priv.focusedAppDelegate && priv.focusedAppDelegate.x !== 0)
323+ focusFirstApp: root.focusFirstApp
296
324
297 readonly property bool isDash: model.appId == "unity8-dash"
325 readonly property bool isDash: model.appId == "unity8-dash"
298
326
@@ -300,7 +328,7 @@ Rectangle {
300
328
301 x: {
329 x: {
302 // focused app is always positioned at 0 except when following left edge drag
330 // focused app is always positioned at 0 except when following left edge drag
303− if (index == 0) {
331+ if (isFocused) {
304 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
332 if (!isDash && root.inverseProgress > 0 && spreadView.phase === 0) {
305 return root.inverseProgress;
333 return root.inverseProgress;
306 }
334 }
@@ -311,10 +339,10 @@ Rectangle {
311 }
339 }
312
340
313 // Otherwise line up for the spread
341 // Otherwise line up for the spread
314− return spreadView.width + (index - 1) * spreadView.tileDistance;
342+ return spreadView.width + spreadIndex * spreadView.tileDistance;
315 }
343 }
316
344
317− application: ApplicationManager.get(index)
345+ application: applicationManager.get(index)
318 closeable: !isDash
346 closeable: !isDash
319
347
320 property real behavioredIndex: index
348 property real behavioredIndex: index
@@ -352,7 +380,7 @@ Rectangle {
352 progress: {
380 progress: {
353 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
381 var tileProgress = (spreadView.shiftedContentX - behavioredIndex * spreadView.tileDistance) / spreadView.width;
354 // Tile 1 needs to move directly from the beginning...
382 // Tile 1 needs to move directly from the beginning...
355− if (behavioredIndex == 1 && spreadView.phase < 2) {
383+ if (root.focusFirstApp && behavioredIndex == 1 && spreadView.phase < 2) {
356 tileProgress += spreadView.tileDistance / spreadView.width;
384 tileProgress += spreadView.tileDistance / spreadView.width;
357 }
385 }
358 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
386 // Limiting progress to ~0 and 1.7 to avoid binding calculations when tiles are not
@@ -367,7 +395,7 @@ Rectangle {
367
395
368 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
396 // This mostly is the same as progress, just adds the snapping to phase 1 for tiles 0 and 1
369 animatedProgress: {
397 animatedProgress: {
370− if (spreadView.phase == 0 && index < 2) {
398+ if (spreadView.phase == 0 && index <= priv.firstSpreadIndex) {
371 if (progress < spreadView.positionMarker1) {
399 if (progress < spreadView.positionMarker1) {
372 return progress;
400 return progress;
373 } else if (progress < spreadView.positionMarker1 + 0.05){
401 } else if (progress < spreadView.positionMarker1 + 0.05){
@@ -392,11 +420,11 @@ Rectangle {
392 }
420 }
393
421
394 onClicked: {
422 onClicked: {
395− if (spreadView.phase == 2) {
423+ if (root.altTabEnabled && spreadView.phase == 2) {
396− if (ApplicationManager.focusedApplicationId == ApplicationManager.get(index).appId) {
424+ if (applicationManager.focusedApplicationId == applicationManager.get(index).appId) {
397 spreadView.snapTo(index);
425 spreadView.snapTo(index);
398 } else {
426 } else {
399− ApplicationManager.requestFocusApplication(ApplicationManager.get(index).appId);
427+ applicationManager.requestFocusApplication(applicationManager.get(index).appId);
400 }
428 }
401 }
429 }
402 }
430 }
@@ -411,7 +439,7 @@ Rectangle {
411
439
412 onClosed: {
440 onClosed: {
413 spreadView.closingIndex = index;
441 spreadView.closingIndex = index;
414− ApplicationManager.stopApplication(ApplicationManager.get(index).appId);
442+ applicationManager.stopApplication(applicationManager.get(index).appId);
415 }
443 }
416 }
444 }
417 }
445 }
@@ -424,7 +452,7 @@ Rectangle {
424 direction: Direction.Leftwards
452 direction: Direction.Leftwards
425 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
453 enabled: (spreadView.phase != 2 && root.spreadEnabled) || dragging
426
454
427− anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
455+ anchors { top: parent.top; right: parent.right; bottom: parent.bottom; rightMargin: -root.dragAreaOverlap }
428 width: root.dragAreaWidth
456 width: root.dragAreaWidth
429
457
430 property var gesturePoints: new Array()
458 property var gesturePoints: new Array()
qml/Stages/TransformedSpreadDelegate.qml+27−21
@@ -46,6 +46,18 @@ SpreadDelegate {
46 property real startDistance: units.gu(5)
46 property real startDistance: units.gu(5)
47 property real endDistance: units.gu(.5)
47 property real endDistance: units.gu(.5)
48
48
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+
49 onSelectedChanged: {
61 onSelectedChanged: {
50 if (selected) {
62 if (selected) {
51 priv.snapshot();
63 priv.snapshot();
@@ -63,8 +75,8 @@ SpreadDelegate {
63 Connections {
75 Connections {
64 target: spreadView
76 target: spreadView
65 onPhaseChanged: {
77 onPhaseChanged: {
66− if (spreadView.phase == 1) {
78+ if (root.focusFirstApp && spreadView.phase == 1) {
67− if (index == 0) {
79+ if (index === 0) {
68 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
80 priv.phase2startTranslate = priv.easingAnimation(0, spreadView.positionMarker4, 0, -spreadView.width, spreadView.positionMarker4) + spreadView.width;
69 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
81 priv.phase2startAngle = priv.easingAnimation(0, spreadView.positionMarker4, root.startAngle, root.endAngle, spreadView.positionMarker4);
70 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
82 priv.phase2startScale = priv.easingAnimation(0, spreadView.positionMarker4, root.startScale, root.endScale, spreadView.positionMarker4);
@@ -134,7 +146,7 @@ SpreadDelegate {
134 }
146 }
135
147
136 if (otherSelected) {
148 if (otherSelected) {
137− if (spreadView.phase < 2 && index == 0) {
149+ if (spreadView.phase < 2 && root.isFocused) {
138 return linearAnimation(selectedProgress, 0, selectedXTranslate,
150 return linearAnimation(selectedProgress, 0, selectedXTranslate,
139 selectedXTranslate - spreadView.tileDistance, root.progress);
151 selectedXTranslate - spreadView.tileDistance, root.progress);
140 }
152 }
@@ -142,8 +154,7 @@ SpreadDelegate {
142 return selectedXTranslate;
154 return selectedXTranslate;
143 }
155 }
144
156
145− switch (index) {
157+ if (root.focusFirstApp && index === 0) {
146− case 0:
147 if (spreadView.phase == 0) {
158 if (spreadView.phase == 0) {
148 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
159 return Math.min(0, linearAnimation(0, spreadView.positionMarker2,
149 0, -spreadView.width * .25, root.animatedProgress));
160 0, -spreadView.width * .25, root.animatedProgress));
@@ -153,11 +164,10 @@ SpreadDelegate {
153 } else if (!priv.isSelected){ // phase 2
164 } else if (!priv.isSelected){ // phase 2
154 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
165 // Apply the same animation as with the rest but add spreadView.width to align it with the others.
155 return -easingCurve.value * spreadView.width + spreadView.width;
166 return -easingCurve.value * spreadView.width + spreadView.width;
156− } else if (priv.isSelected) {
167+ } else {
157 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
168 return linearAnimation(selectedProgress, 0, selectedXTranslate, 0, root.progress);
158 }
169 }
159−
170+ } else if (root.focusFirstApp && index === 1) {
160− case 1:
161 if (spreadView.phase == 0 && !priv.isSelected) {
171 if (spreadView.phase == 0 && !priv.isSelected) {
162 return linearAnimation(0, spreadView.positionMarker2,
172 return linearAnimation(0, spreadView.positionMarker2,
163 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
173 0, -spreadView.width * spreadView.snapPosition, root.animatedProgress);
@@ -170,13 +180,13 @@ SpreadDelegate {
170
180
171 if (priv.isSelected) {
181 if (priv.isSelected) {
172 // Distance to left edge
182 // Distance to left edge
173− var targetTranslate = -spreadView.width - ((index - 1) * root.startDistance);
183+ var targetTranslate = -spreadView.width - (root.spreadIndex * root.startDistance);
174 return linearAnimation(selectedProgress, 0,
184 return linearAnimation(selectedProgress, 0,
175 selectedXTranslate, targetTranslate, root.progress);
185 selectedXTranslate, targetTranslate, root.progress);
176 }
186 }
177
187
178 // Fix it at the right edge...
188 // Fix it at the right edge...
179− var rightEdgeOffset = -((index - 1) * root.startDistance);
189+ var rightEdgeOffset = -(root.spreadIndex * root.startDistance);
180 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
190 // ...and use our easing to move them to the left. Stop a bit earlier for each tile
181 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
191 var animatedEndDistance = linearAnimation(0, 2, root.endDistance, 0, root.progress);
182 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
192 return -easingCurve.value * spreadView.width + (index * animatedEndDistance) + rightEdgeOffset;
@@ -194,8 +204,7 @@ SpreadDelegate {
194 if (priv.isSelected) {
204 if (priv.isSelected) {
195 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
205 return linearAnimation(selectedProgress, 0, selectedAngle, 0, root.progress);
196 }
206 }
197− switch (index) {
207+ if (root.focusFirstApp && index === 0) {
198− case 0:
199 if (spreadView.phase == 0) {
208 if (spreadView.phase == 0) {
200 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
209 return Math.max(0, linearAnimation(0, spreadView.positionMarker2,
201 0, root.tile0SnapAngle, root.animatedProgress));
210 0, root.tile0SnapAngle, root.animatedProgress));
@@ -203,7 +212,7 @@ SpreadDelegate {
203 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
212 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
204 root.tile0SnapAngle, phase2startAngle, root.progress);
213 root.tile0SnapAngle, phase2startAngle, root.progress);
205 }
214 }
206− case 1:
215+ } else if (root.focusFirstApp && index === 1) {
207 if (spreadView.phase == 0) {
216 if (spreadView.phase == 0) {
208 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
217 return linearAnimation(0, spreadView.positionMarker2, root.startAngle,
209 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
218 root.startAngle * (1-spreadView.snapPosition), root.animatedProgress);
@@ -227,15 +236,14 @@ SpreadDelegate {
227 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
236 return linearAnimation(selectedProgress, 0, selectedScale, 1, root.progress);
228 }
237 }
229
238
230− switch (index) {
239+ if (root.focusFirstApp && index === 0) {
231− case 0:
232 if (spreadView.phase == 0) {
240 if (spreadView.phase == 0) {
233 return 1;
241 return 1;
234 } else if (spreadView.phase == 1) {
242 } else if (spreadView.phase == 1) {
235 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
243 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
236 1, phase2startScale, root.progress);
244 1, phase2startScale, root.progress);
237 }
245 }
238− case 1:
246+ } else if (root.focusFirstApp && index === 1) {
239 if (spreadView.phase == 0) {
247 if (spreadView.phase == 0) {
240 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
248 var targetScale = tile1StartScale - ((tile1StartScale - 1) * spreadView.snapPosition);
241 return linearAnimation(0, spreadView.positionMarker2,
249 return linearAnimation(0, spreadView.positionMarker2,
@@ -254,7 +262,7 @@ SpreadDelegate {
254 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
262 return linearAnimation (selectedProgress, Math.max(0, selectedProgress - .5),
255 selectedOpacity, 0, root.progress);
263 selectedOpacity, 0, root.progress);
256 }
264 }
257− if (index == 0) {
265+ if (root.isFocused) {
258 switch (spreadView.phase) {
266 switch (spreadView.phase) {
259 case 0:
267 case 0:
260 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
268 return linearAnimation(0, spreadView.positionMarker2, 1, .7, root.animatedProgress);
@@ -272,16 +280,14 @@ SpreadDelegate {
272 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
280 return linearAnimation(selectedProgress, 0, selectedTopMarginProgress, 0, root.progress);
273 }
281 }
274
282
275− switch (index) {
283+ if (root.focusFirstApp && index === 0) {
276− case 0:
277 if (spreadView.phase == 0) {
284 if (spreadView.phase == 0) {
278 return 0;
285 return 0;
279 } else if (spreadView.phase == 1) {
286 } else if (spreadView.phase == 1) {
280 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
287 return linearAnimation(spreadView.positionMarker2, spreadView.positionMarker4,
281 0, priv.phase2startTopMarginProgress, root.progress);
288 0, priv.phase2startTopMarginProgress, root.progress);
282 }
289 }
283− break;
290+ } else if (root.focusFirstApp && index === 1) {
284− case 1:
285 if (spreadView.phase == 0) {
291 if (spreadView.phase == 0) {
286 return 0;
292 return 0;
287 } else if (spreadView.phase == 1) {
293 } else if (spreadView.phase == 1) {
qml/Tutorial/Slider.qml+8−2
@@ -38,7 +38,7 @@ Item {
38 readonly property real margin: units.gu(0.5)
38 readonly property real margin: units.gu(0.5)
39 readonly property real arrowSize: root.height - margin * 2
39 readonly property real arrowSize: root.height - margin * 2
40 readonly property real dotSize: units.dp(1)
40 readonly property real dotSize: units.dp(1)
41− readonly property real slideOffset: Math.min(root.offset - offscreenOffset, target.x)
41+ readonly property real slideOffset: MathUtils.clamp(root.offset - offscreenOffset, -offscreenOffset, target.x)
42 readonly property real offscreenOffset: units.gu(2)
42 readonly property real offscreenOffset: units.gu(2)
43 }
43 }
44
44
@@ -111,7 +111,13 @@ Item {
111 color: UbuntuColors.orange
111 color: UbuntuColors.orange
112 darkenBy: root.active ? 0.5 : 0
112 darkenBy: root.active ? 0.5 : 0
113 anchors.left: parent.left
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 {
114− anchors.leftMargin: d.slideOffset
118+ x: d.slideOffset
119+ }
120+ ]
115 anchors.verticalCenter: parent.verticalCenter
121 anchors.verticalCenter: parent.verticalCenter
116 }
122 }
117}
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.qml+8−1
@@ -22,6 +22,7 @@ Item {
22
22
23 property alias active: loader.active
23 property alias active: loader.active
24 property bool paused
24 property bool paused
25+ property real edgeSize
25
26
26 property Item launcher
27 property Item launcher
27 property Item panel
28 property Item panel
@@ -29,7 +30,7 @@ Item {
29 property Item overlay
30 property Item overlay
30
31
31 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
32 readonly property bool launcherEnabled: loader.item ? loader.item.launcherEnabled : true
32− readonly property bool stagesEnabled: loader.item ? loader.item.stagesEnabled : true
33+ readonly property bool spreadEnabled: loader.item ? loader.item.spreadEnabled : true
33 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
34 readonly property bool panelEnabled: loader.item ? loader.item.panelEnabled : true
34 readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
35 readonly property bool panelContentEnabled: loader.item ? loader.item.panelContentEnabled : true
35 readonly property bool running: loader.item ? loader.item.running : false
36 readonly property bool running: loader.item ? loader.item.running : false
@@ -53,6 +54,12 @@ Item {
53 value: root.paused
54 value: root.paused
54 }
55 }
55
56
57+ Binding {
58+ target: loader.item
59+ property: "edgeSize"
60+ value: root.edgeSize
61+ }
62+
56 Binding {
63 Binding {
57 target: loader.item
64 target: loader.item
58 property: "launcher"
65 property: "launcher"
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.qml+55−61
@@ -26,13 +26,14 @@ Item {
26 property Item overlay
26 property Item overlay
27
27
28 readonly property bool launcherEnabled: !running ||
28 readonly property bool launcherEnabled: !running ||
29− (!paused && loader.target === leftComponent)
29+ (!paused && tutorialLeft.shown)
30− readonly property bool stagesEnabled: !running
30+ readonly property bool spreadEnabled: !running
31 readonly property bool panelEnabled: !running
31 readonly property bool panelEnabled: !running
32 readonly property bool panelContentEnabled: !running
32 readonly property bool panelContentEnabled: !running
33− readonly property bool running: loader.sourceComponent !== null
33+ readonly property alias running: d.running
34
34
35 property bool paused: false
35 property bool paused: false
36+ property real edgeSize
36
37
37 signal finished()
38 signal finished()
38
39
@@ -50,81 +51,74 @@ Item {
50 QtObject {
51 QtObject {
51 id: d
52 id: d
52
53
54+ property bool running
55+
53 function stop() {
56 function stop() {
54− loader.sourceComponent = null;
57+ running = false;
55 }
58 }
56
59
57 function start() {
60 function start() {
58− loader.load(leftComponent);
61+ running = true;
62+ tutorialLeft.show();
59 }
63 }
60 }
64 }
61
65
62− Loader {
66+ TutorialLeft {
63− id: loader
67+ id: tutorialLeft
64− objectName: "tutorialLoader"
68+ objectName: "tutorialLeft"
65−
69+ parent: root.stages
66− property Component target: {
70+ anchors.fill: parent
67− if (next) {
71+ launcher: root.launcher
68− return next;
72+ paused: !shown || root.paused
69− } else if (loader.item && loader.item.shown) {
70− return sourceComponent;
71− } else {
72− return null;
73− }
74− }
75
73
76− property Component next: null
74+ onFinished: tutorialLeftFinish.show()
75+ }
77
76
78− function load(comp) {
77+ TutorialLeftFinish {
79− if (loader.item) {
78+ id: tutorialLeftFinish
80− next = comp;
79+ objectName: "tutorialLeftFinish"
81− loader.item.hide();
80+ parent: root.stages
82− } else {
81+ anchors.fill: parent
83− loader.sourceComponent = comp;
82+ textXOffset: root.launcher.panelWidth
84− }
83+ paused: !shown || root.paused
84+
85+ onFinished: {
86+ root.launcher.hide();
87+ tutorialRight.show();
85 }
88 }
89+ }
86
90
87− Connections {
91+ TutorialRight {
88− target: loader.item
92+ id: tutorialRight
89− onFinished: {
93+ objectName: "tutorialRight"
90− loader.sourceComponent = loader.next;
94+ parent: root.stages
91− if (loader.next != null) {
95+ anchors.fill: parent
92− loader.next = null;
96+ edgeSize: root.edgeSize
93− } else {
97+ panel: root.panel
94− root.finished();
98+ paused: !shown || root.paused
95− }
96− }
97− }
98
99
99− Binding {
100+ onFinished: tutorialBottom.show()
100− target: loader.item
101− property: "paused"
102− value: root.paused
103− }
104 }
101 }
105
102
106− Component {
103+ TutorialBottom {
107− id: leftComponent
104+ id: tutorialBottom
108− TutorialLeft {
109− objectName: "tutorialLeft"
105+ objectName: "tutorialBottom"
110− parent: root.stages
106+ parent: root.stages
111− anchors.fill: parent
107+ anchors.fill: parent
112− launcher: root.launcher
108+ edgeSize: root.edgeSize
109+ paused: !shown || root.paused
113
110
114− onFinished: loader.load(leftFinishComponent)
111+ onFinished: tutorialBottomFinish.show()
115− }
116 }
112 }
117
113
118− Component {
114+ TutorialBottomFinish {
119− id: leftFinishComponent
115+ id: tutorialBottomFinish
120− TutorialLeftFinish {
116+ objectName: "tutorialBottomFinish"
121− objectName: "tutorialLeftFinish"
122− parent: root.stages
117+ parent: root.stages
123− anchors.fill: parent
118+ anchors.fill: parent
124− textXOffset: root.launcher.panelWidth
125− backgroundFadesOut: true
119+ backgroundFadesOut: true
120+ paused: !shown || root.paused
126
121
127− onFinished: root.launcher.hide()
122+ onFinished: root.finish()
128− }
129 }
123 }
130}
124}
qml/Tutorial/TutorialLeftFinish.qml+4−10
@@ -16,31 +16,25 @@
16
16
17import QtQuick 2.3
17import QtQuick 2.3
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19+import "." as LocalComponents
19
20
20TutorialPage {
21TutorialPage {
21 id: root
22 id: root
22
23
23 title: i18n.tr("These are the shortcuts to favorite apps")
24 title: i18n.tr("These are the shortcuts to favorite apps")
24− text: i18n.tr("Tap here to finish.")
25+ text: i18n.tr("Tap here to continue.")
25 fullTextWidth: true
26 fullTextWidth: true
26
27
27 foreground {
28 foreground {
28 children: [
29 children: [
29− Image {
30+ LocalComponents.Tick {
30 objectName: "tick"
31 objectName: "tick"
31 anchors {
32 anchors {
32 horizontalCenter: parent.horizontalCenter
33 horizontalCenter: parent.horizontalCenter
33 top: parent.top
34 top: parent.top
34 topMargin: root.textBottom + units.gu(3)
35 topMargin: root.textBottom + units.gu(3)
35 }
36 }
36− source: Qt.resolvedUrl("graphics/tick.png")
37− height: units.gu(6.5)
38− width: units.gu(6.5)
39−
40− MouseArea {
41− anchors.fill: parent
42− onClicked: root.hide()
37+ onClicked: root.hide()
43− }
44 }
38 }
45 ]
39 ]
46 }
40 }
qml/Tutorial/TutorialPage.qml+72−67
@@ -52,8 +52,8 @@ Showable {
52 property real textXOffset: 0
52 property real textXOffset: 0
53 property real textYOffset: 0
53 property real textYOffset: 0
54
54
55− // Foreground opacity
55+ // Foreground text opacity
56− property real foregroundOpacity: 1
56+ property real textOpacity: 1
57
57
58 signal finished()
58 signal finished()
59
59
@@ -63,8 +63,8 @@ Showable {
63
63
64 ////
64 ////
65
65
66+ visible: false
66 shown: false
67 shown: false
67− Component.onCompleted: show()
68
68
69 property real _foregroundHideOpacity
69 property real _foregroundHideOpacity
70
70
@@ -73,16 +73,16 @@ Showable {
73 from: 0
73 from: 0
74 to: 1
74 to: 1
75 duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
75 duration: root.backgroundFadesIn ? UbuntuAnimation.SleepyDuration : UbuntuAnimation.BriskDuration
76+ onStarted: root.visible = true
76 }
77 }
77
78
78 hideAnimation: StandardAnimation {
79 hideAnimation: StandardAnimation {
79 property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
80 property: root.backgroundFadesOut ? "opacity" : "_foregroundHideOpacity"
80 to: 0
81 to: 0
81 duration: UbuntuAnimation.BriskDuration
82 duration: UbuntuAnimation.BriskDuration
82− onRunningChanged: {
83+ onStopped: {
83− if (!running) {
84+ root.visible = false;
84− root.finished();
85+ root.finished();
85− }
86 }
86 }
87 }
87 }
88
88
@@ -122,73 +122,78 @@ Showable {
122 Item {
122 Item {
123 id: foreground
123 id: foreground
124 anchors.fill: parent
124 anchors.fill: parent
125− opacity: root.foregroundOpacity < 1 ? root.foregroundOpacity : root._foregroundHideOpacity
125+ opacity: root._foregroundHideOpacity
126−
126+
127+ Item {
128+ anchors.fill: parent
129+ opacity: root.textOpacity
130+
127− Label {
131+ Label {
128− id: titleLabel
132+ id: titleLabel
129− anchors {
133+ anchors {
130− top: parent.verticalCenter
134+ top: parent.verticalCenter
131− topMargin: d.verticalOffset + root.textYOffset
135+ topMargin: d.verticalOffset + root.textYOffset
132− left: parent.left
136+ left: parent.left
133− leftMargin: d.sideMargin + d.textXOffset
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)
134 }
144 }
135− width: parent.width - d.sideMargin * 2
136− horizontalAlignment: Text.AlignLeft
137− wrapMode: Text.Wrap
138− font.weight: Font.Light
139− font.pixelSize: units.gu(3.5)
140− }
141
145
142− Label {
146+ Label {
143− id: textLabel
147+ id: textLabel
144− anchors {
148+ anchors {
145− top: titleLabel.bottom
149+ top: titleLabel.bottom
146− topMargin: units.gu(2)
150+ topMargin: units.gu(2)
147− left: parent.left
151+ left: parent.left
148− leftMargin: d.sideMargin + d.textXOffset
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)
149 }
159 }
150− width: (parent.width - d.sideMargin * 2) * (fullTextWidth ? 1 : 0.66)
151− horizontalAlignment: Text.AlignLeft
152− wrapMode: Text.Wrap
153− font.weight: Font.Light
154− font.pixelSize: units.gu(2.5)
155− }
156
160
157− // We use two separate labels like this rather than just changing
161+ // We use two separate labels like this rather than just changing
158− // the text of the above labels because we want to know where to place
162+ // the text of the above labels because we want to know where to place
159− // sliders (via root.textBottom) without having that place change
163+ // sliders (via root.textBottom) without having that place change
160− // as the text changes length.
164+ // as the text changes length.
161− Label {
165+ Label {
162− id: errorTitleLabel
166+ id: errorTitleLabel
163− objectName: "errorTitleLabel"
167+ objectName: "errorTitleLabel"
164− anchors {
168+ anchors {
165− top: titleLabel.top
169+ top: titleLabel.top
166− left: titleLabel.left
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!")
167 }
179 }
168− width: titleLabel.width
169− horizontalAlignment: titleLabel.horizontalAlignment
170− wrapMode: titleLabel.wrapMode
171− font.weight: titleLabel.font.weight
172− font.pixelSize: titleLabel.font.pixelSize
173− opacity: 0
174− text: i18n.tr("You almost got it!")
175− }
176
180
177− Label {
181+ Label {
178− id: errorTextLabel
182+ id: errorTextLabel
179− objectName: "errorTextLabel"
183+ objectName: "errorTextLabel"
180− anchors {
184+ anchors {
181− top: errorTitleLabel.bottom
185+ top: errorTitleLabel.bottom
182− topMargin: textLabel.anchors.topMargin
186+ topMargin: textLabel.anchors.topMargin
183− left: textLabel.left
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.")
184 }
196 }
185− width: textLabel.width
186− horizontalAlignment: textLabel.horizontalAlignment
187− wrapMode: textLabel.wrapMode
188− font.weight: textLabel.font.weight
189− font.pixelSize: textLabel.font.pixelSize
190− opacity: 0
191− text: i18n.tr("Try again.")
192 }
197 }
193
198
194 // A place for subclasses to add extra widgets
199 // A place for subclasses to add extra widgets
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/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.

tests/autopilot/unity8/shell/emulators/tutorial.py+24−4
@@ -35,20 +35,40 @@ class TutorialPage(
35 @classmethod
35 @classmethod
36 def validate_dbus_object(cls, path, state):
36 def validate_dbus_object(cls, path, state):
37 name = introspection.get_classname_from_path(path)
37 name = introspection.get_classname_from_path(path)
38− return name == b'TutorialPage' or name == b'TutorialLeft'
38+ return name in (b'TutorialPage', b'TutorialLeft',
39+ b'TutorialLeftFinish', b'TutorialRight',
40+ b'TutorialBottom', b'TutorialBottomFinish')
39
41
40 @autopilot.logging.log_action(logger.info)
42 @autopilot.logging.log_action(logger.info)
41 def short_swipe_right(self):
43 def short_swipe_right(self):
44+ self.shown.wait_for(True)
42 x, y, width, height = self.globalRect
45 x, y, width, height = self.globalRect
43 start_x = x
46 start_x = x
44 stop_x = x + width // 3
47 stop_x = x + width // 3
45 start_y = stop_y = y + height // 2
48 start_y = stop_y = y + height // 2
46 self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
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):
47− self.shown.wait_for(False)
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)
48
68
49 @autopilot.logging.log_action(logger.info)
69 @autopilot.logging.log_action(logger.info)
50 def tap(self):
70 def tap(self):
51 """Tap the tick button to complete this step."""
71 """Tap the tick button to complete this step."""
72+ self.shown.wait_for(True)
52− button = self.select_single(objectName="tick")
73+ button = self.wait_select_single(objectName="tick")
53 self.pointing_device.click_object(button)
74 self.pointing_device.click_object(button)
54− self.shown.wait_for(False)
tests/autopilot/unity8/shell/tests/test_tutorial.py+7
@@ -47,4 +47,11 @@ class TutorialTestCase(tests.UnityTestCase):
47 page.short_swipe_right()
47 page.short_swipe_right()
48 page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
48 page = self.unity.wait_select_single(objectName='tutorialLeftFinish')
49 page.tap()
49 page.tap()
50+ page = self.unity.wait_select_single(objectName='tutorialRight')
51+ page.swipe_left()
52+ page.tap()
53+ page = self.unity.wait_select_single(objectName='tutorialBottom')
54+ page.swipe_up()
55+ page = self.unity.wait_select_single(objectName='tutorialBottomFinish')
56+ page.tap()
50 self.assertThat(tutorial.running, Eventually(Equals(False)))
57 self.assertThat(tutorial.running, Eventually(Equals(False)))
tests/qmltests/Tutorial/tst_Tutorial.qml+63−1
@@ -162,7 +162,6 @@ Item {
162 }
162 }
163
163
164 function waitForPage(name) {
164 function waitForPage(name) {
165− tryCompareFunction(function() { return findChild(shell, name) !== null; }, true);
166 waitForRendering(findChild(shell, name));
165 waitForRendering(findChild(shell, name));
167 var page = findChild(shell, name);
166 var page = findChild(shell, name);
168 tryCompare(page, "shown", true);
167 tryCompare(page, "shown", true);
@@ -221,6 +220,33 @@ Item {
221 var tick = findChild(page, "tick");
220 var tick = findChild(page, "tick");
222 tap(tick);
221 tap(tick);
223
222
223+ page = waitForPage("tutorialRight");
224+ checkTopEdge();
225+ checkLeftEdge();
226+ checkBottomEdge();
227+ if (name === "tutorialRight") return page;
228+ touchFlick(shell, shell.width, halfHeight, halfWidth, halfHeight);
229+ var overlay = findChild(page, "overlay");
230+ tryCompare(overlay, "shown", true);
231+ var tick = findChild(page, "tick");
232+ tap(tick);
233+
234+ var page = waitForPage("tutorialBottom");
235+ checkTopEdge();
236+ checkLeftEdge();
237+ checkRightEdge();
238+ if (name === "tutorialBottom") return page;
239+ touchFlick(shell, halfWidth, shell.height, halfWidth, halfHeight);
240+
241+ var page = waitForPage("tutorialBottomFinish");
242+ checkTopEdge();
243+ checkLeftEdge();
244+ checkRightEdge();
245+ checkBottomEdge();
246+ if (name === "tutorialBottomFinish") return page;
247+ var tick = findChild(page, "tick");
248+ tap(tick);
249+
224 checkFinished();
250 checkFinished();
225 return null;
251 return null;
226 }
252 }
@@ -277,6 +303,42 @@ Item {
277 tryCompare(left, "shown", true); // and we should still be on left
303 tryCompare(left, "shown", true); // and we should still be on left
278 }
304 }
279
305
306+ function test_spread() {
307+ // Unfortunately, most of what we want to test of the spread is
308+ // "did it render correctly?" but that's hard to test. So instead,
309+ // just poke and prod it a little bit to see if some of the values
310+ // we'd expect to be correct, are so.
311+
312+ var right = goToPage("tutorialRight");
313+ var stage = findChild(right, "stage");
314+ var delegate0 = findChild(right, "appDelegate0");
315+
316+ tryCompare(stage, "dragProgress", 0);
317+ touchFlick(shell, shell.width, halfHeight, shell.width * 0.8, halfHeight, true, false);
318+ verify(stage.dragProgress > 0);
319+ compare(stage.dragProgress, -delegate0.xTranslate);
320+ touchFlick(shell, shell.width * 0.8, halfHeight, shell.width, halfHeight, false, true);
321+ tryCompare(stage, "dragProgress", 0);
322+
323+ tryCompare(delegate0, "x", shell.width);
324+
325+ var screenshotImage = findChild(right, "screenshotImage");
326+ tryCompare(screenshotImage, "source", Qt.resolvedUrl("../../../qml/Tutorial/graphics/facebook.png"));
327+ tryCompare(screenshotImage, "visible", true);
328+ }
329+
330+ function test_bottomShortDrag() {
331+ var bottom = goToPage("tutorialBottom");
332+
333+ touchFlick(shell, halfWidth, shell.height, halfWidth, shell.height * 0.8);
334+
335+ var errorTextLabel = findChild(bottom, "errorTextLabel");
336+ var errorTitleLabel = findChild(bottom, "errorTitleLabel");
337+ tryCompare(bottom, "shown", true); // still on bottom page
338+ tryCompare(errorTextLabel, "opacity", 1); // show error
339+ tryCompare(errorTitleLabel, "opacity", 1); // show error
340+ }
341+
280 function test_interrupted() {
342 function test_interrupted() {
281 goToPage("tutorialLeft");
343 goToPage("tutorialLeft");
282 ApplicationManager.startApplication("dialer-app");
344 ApplicationManager.startApplication("dialer-app");