on xenial help sign in
Make swipe teases in the greeter more helpful and obvious Fixes: 1267623, 1308475, 1308477
commita6f2b2dc66ae372b97ee8536b4166d999f028576 authorMichael Terry <michael.terry@canonical.com> dateTue 29 Apr 2014, 15:18 (12 years ago) parent5fe767a4f64c2d
10 files changed+232−159
po/unity8.pot+7−3 qml/Greeter/Greeter.qml+115−42 qml/Greeter/GreeterContent.qml+1−10 qml/Greeter/Infographics.qml+3 qml/Hud/Hud.qml+1−1 qml/Shell.qml+59−45 qml/graphics/dropshadow_left@20.png qml/Hud/graphics/dropshadow_right@20.png → qml/graphics/dropshadow_right@20.png tests/qmltests/Greeter/tst_Phone.qml+10−50 tests/qmltests/Greeter/tst_Tablet.qml+36−8
po/unity8.pot+7−3
@@ -8,7 +8,7 @@ msgid ""
88msgstr ""
99"Project-Id-Version: unity8\n"
1010"Report-Msgid-Bugs-To: \n"
11−"POT-Creation-Date: 2014-03-25 16:48+0100\n"
11+"POT-Creation-Date: 2014-04-10 11:13-0400\n"
1212"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -84,7 +84,7 @@ msgstr ""
8484msgid "See less"
8585msgstr ""
8686
87−#: qml/Dash/DashApps.qml:38
87+#: qml/Dash/DashApps.qml:37
8888msgid "Recent"
8989msgstr ""
9090
@@ -113,6 +113,10 @@ msgstr ""
113113msgid "Send"
114114msgstr ""
115115
116+#: qml/Greeter/Greeter.qml:151
117+msgid "Swipe to unlock"
118+msgstr ""
119+
116120#: qml/Greeter/Lockscreen.qml:149
117121msgid "Emergency Call"
118122msgstr ""
@@ -158,7 +162,7 @@ msgstr ""
158162msgid "Search"
159163msgstr ""
160164
161−#: qml/Shell.qml:417
165+#: qml/Shell.qml:348
162166#, qt-format
163167msgid "Please enter %1"
164168msgstr ""
qml/Greeter/Greeter.qml+115−42
@@ -28,10 +28,10 @@ Showable {
2828 property url defaultBackground
2929
3030 // 1 when fully shown and 0 when fully hidden
31− property real showProgress: MathUtils.clamp((width + x) / width, 0, 1)
31+ property real showProgress: MathUtils.clamp((width - Math.abs(x)) / width, 0, 1)
3232
3333 showAnimation: StandardAnimation { property: "x"; to: 0 }
34− hideAnimation: StandardAnimation { property: "x"; to: -width }
34+ hideAnimation: __leftHideAnimation
3535
3636 property alias dragHandleWidth: dragHandle.width
3737 property alias model: greeterContentLoader.model
@@ -40,19 +40,81 @@ Showable {
4040 readonly property bool narrowMode: !multiUser && height > width
4141 readonly property bool multiUser: LightDM.Users.count > 1
4242
43− readonly property bool leftTeaserPressed: greeterContentLoader.status == Loader.Ready &&
44− greeterContentLoader.item.leftTeaserPressed
45− readonly property bool rightTeaserPressed: greeterContentLoader.status == Loader.Ready &&
46− greeterContentLoader.item.rightTeaserPressed
47−
4843 readonly property int currentIndex: greeterContentLoader.currentIndex
4944
45+ property var __leftHideAnimation: StandardAnimation { property: "x"; to: -width }
46+ property var __rightHideAnimation: StandardAnimation { property: "x"; to: width }
47+
5048 signal selected(int uid)
5149 signal unlocked(int uid)
50+ signal tease()
51+
52+ function hideRight() {
53+ if (shown) {
54+ hideAnimation = __rightHideAnimation
55+ hide()
56+ }
57+ }
58+
59+ onRequiredChanged: {
60+ // Reset hide animation to default once we're finished with it
61+ if (!required) {
62+ // Put back on left for reliable show direction and so that
63+ // if normal hide() is called, we don't animate from right.
64+ x = -width
65+ hideAnimation = __leftHideAnimation
66+ }
67+ }
68+
69+ // Bi-directional revealer
70+ DraggingArea {
71+ id: dragHandle
72+ anchors.fill: parent
73+ enabled: greeter.narrowMode || !greeter.locked
74+ orientation: Qt.Horizontal
75+ propagateComposedEvents: true
76+
77+ Component.onCompleted: {
78+ // set evaluators to baseline of dragValue == 0
79+ leftEvaluator.reset()
80+ rightEvaluator.reset()
81+ }
82+
83+ function maybeTease() {
84+ if (!greeter.locked || greeter.narrowMode)
85+ greeter.tease();
86+ }
87+
88+ onClicked: maybeTease()
89+ onDragStart: maybeTease()
90+ onPressAndHold: {} // eat event, but no need to tease, as drag will cover it
91+
92+ onDragEnd: {
93+ if (rightEvaluator.shouldAutoComplete())
94+ greeter.hideRight()
95+ else if (leftEvaluator.shouldAutoComplete())
96+ greeter.hide();
97+ else
98+ greeter.show(); // undo drag
99+ }
100+
101+ onDragValueChanged: {
102+ // dragValue is kept as a "step" value since we do this x adjusting on the fly
103+ greeter.x += dragValue
104+ }
105+
106+ EdgeDragEvaluator {
107+ id: rightEvaluator
108+ trackedPosition: dragHandle.dragValue + greeter.x
109+ maxDragDistance: parent.width
110+ direction: Direction.Rightwards
111+ }
52112
53− onRightTeaserPressedChanged: {
54− if (rightTeaserPressed && (!locked || narrowMode) && x == 0) {
55− teasingTimer.start();
113+ EdgeDragEvaluator {
114+ id: leftEvaluator
115+ trackedPosition: dragHandle.dragValue + greeter.x
116+ maxDragDistance: parent.width
117+ direction: Direction.Leftwards
56118 }
57119 }
58120
@@ -82,45 +144,56 @@ Showable {
82144 }
83145 }
84146
85− Timer {
86− id: teasingTimer
87− interval: 200
88− }
147+ onTease: showLabelAnimation.start()
89148
90− states: [
91− State {
92− name: "teasing"
93− when: teasingTimer.running
94− PropertyChanges {
95− target: greeter
96− x: -dragHandle.hintDisplacement
149+ Label {
150+ id: swipeHint
151+ visible: greeter.shown
152+ property real baseOpacity: 0.5
153+ opacity: 0.0
154+ anchors.horizontalCenter: parent.horizontalCenter
155+ anchors.bottom: parent.bottom
156+ anchors.bottomMargin: units.gu(5)
157+ text: i18n.tr("Swipe to unlock")
158+ color: "white"
159+ font.weight: Font.Light
160+
161+ SequentialAnimation on opacity {
162+ id: showLabelAnimation
163+ running: false
164+ loops: 2
165+
166+ StandardAnimation {
167+ from: 0.0
168+ to: swipeHint.baseOpacity
169+ duration: UbuntuAnimation.SleepyDuration
97170 }
98− }
99− ]
100− transitions: [
101− Transition {
102− from: "*"
103− to: "*"
104− NumberAnimation {
105− target: greeter
106− property: "x"
107− duration: 300
108− easing.type: Easing.OutCubic
171+ PauseAnimation { duration: UbuntuAnimation.BriskDuration }
172+ StandardAnimation {
173+ from: swipeHint.baseOpacity
174+ to: 0.0
175+ duration: UbuntuAnimation.SleepyDuration
109176 }
110177 }
111− ]
112−
113− DragHandle {
114− id: dragHandle
178+ }
115179
180+ // right side shadow
181+ Image {
182+ anchors.left: parent.right
116183 anchors.top: parent.top
117184 anchors.bottom: parent.bottom
118− anchors.right: parent.right
119−
120− hintDisplacement: units.gu(2)
121−
122− enabled: greeter.narrowMode || !greeter.locked
185+ visible: parent.required
186+ fillMode: Image.Tile
187+ source: "../graphics/dropshadow_right.png"
188+ }
123189
124− direction: Direction.Leftwards
190+ // left side shadow
191+ Image {
192+ anchors.right: parent.left
193+ anchors.top: parent.top
194+ anchors.bottom: parent.bottom
195+ visible: parent.required
196+ fillMode: Image.Tile
197+ source: "../graphics/dropshadow_left.png"
125198 }
126199}
qml/Greeter/GreeterContent.qml+1−10
@@ -20,15 +20,11 @@ import Ubuntu.Components 0.1
2020import LightDM 0.1 as LightDM
2121import "../Components"
2222
23−MouseArea {
23+Item {
2424 id: root
2525 anchors.fill: parent
2626
2727 property bool ready: background.source == "" || background.status == Image.Ready || background.status == Image.Error
28− property bool leftTeaserPressed: teasingMouseArea.pressed &&
29− teasingMouseArea.mouseX < teasingMouseArea.width / 2
30− property bool rightTeaserPressed: teasingMouseArea.pressed &&
31− teasingMouseArea.mouseX > teasingMouseArea.width / 2
3228
3329 signal selected(int uid)
3430 signal unlocked(int uid)
@@ -74,11 +70,6 @@ MouseArea {
7470 opacity: 0.4
7571 }
7672
77− MouseArea {
78− id: teasingMouseArea
79− anchors.fill: parent
80− }
81−
8273 Loader {
8374 id: loginLoader
8475 objectName: "loginLoader"
qml/Greeter/Infographics.qml+3
@@ -457,5 +457,8 @@ Item {
457457 !circleGrowAnimTimer.running)
458458 infographic.model.nextDataSource()
459459 }
460+
461+ onClicked: mouse.accepted = false
462+ onPressed: mouse.accepted = false
460463 }
461464}
qml/Hud/Hud.qml+1−1
@@ -491,6 +491,6 @@ Item {
491491 anchors.top: hudContentClipper.top
492492 anchors.bottom: hudContentClipper.bottom
493493 fillMode: Image.Tile
494− source: "graphics/dropshadow_right.png"
494+ source: "../graphics/dropshadow_right.png"
495495 }
496496}
qml/Shell.qml+59−45
@@ -148,8 +148,8 @@ FocusScope {
148148
149149 available: !greeter.shown && !lockscreen.shown
150150 hides: [stages, launcher, panel.indicators]
151− shown: disappearingAnimationProgress !== 1.0
152− enabled: disappearingAnimationProgress === 0.0 && edgeDemo.dashEnabled
151+ shown: disappearingAnimationProgress !== 1.0 && greeterWrapper.showProgress !== 1.0
152+ enabled: disappearingAnimationProgress === 0.0 && greeterWrapper.showProgress === 0.0 && edgeDemo.dashEnabled
153153
154154 anchors {
155155 fill: parent
@@ -159,18 +159,14 @@ FocusScope {
159159 contentScale: 1.0 - 0.2 * disappearingAnimationProgress
160160 opacity: 1.0 - disappearingAnimationProgress
161161 property real disappearingAnimationProgress: {
162− if (greeter.shown) {
163− return greeter.showProgress;
162+ if (stages.overlayMode) {
163+ return 0;
164164 } else {
165− if (stages.overlayMode) {
166− return 0;
167− }
168165 return stages.showProgress
169166 }
170167 }
171168
172− // FIXME: only necessary because stages.showProgress and
173− // greeterRevealer.animatedProgress are not animated
169+ // FIXME: only necessary because stages.showProgress is not animated
174170 Behavior on disappearingAnimationProgress { SmoothedAnimation { velocity: 5 }}
175171 }
176172 }
@@ -362,52 +358,66 @@ FocusScope {
362358 }
363359 }
364360
365− Greeter {
366− id: greeter
367− objectName: "greeter"
368−
369− available: true
370− hides: [launcher, panel.indicators, hud]
371− shown: true
372−
373− defaultBackground: shell.background
361+ Rectangle {
362+ anchors.fill: parent
363+ color: "black"
364+ opacity: greeterWrapper.showProgress * 0.8
365+ }
374366
367+ Item {
368+ // Just a tiny wrapper to adjust greeter's x without messing with its own dragging
369+ id: greeterWrapper
370+ x: launcher.progress
375371 y: panel.panelHeight
376372 width: parent.width
377373 height: parent.height - panel.panelHeight
378374
379− dragHandleWidth: shell.edgeSize
375+ Behavior on x {SmoothedAnimation{velocity: 600}}
380376
381− onShownChanged: {
382− if (shown) {
383− lockscreen.reset();
384− // If there is only one user, we start authenticating with that one here.
385− // If there are more users, the Greeter will handle that
386− if (LightDM.Users.count == 1) {
387− LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
377+ readonly property real showProgress: MathUtils.clamp((1 - x/width) + greeter.showProgress - 1, 0, 1)
378+
379+ Greeter {
380+ id: greeter
381+ objectName: "greeter"
382+
383+ available: true
384+ hides: [launcher, panel.indicators, hud]
385+ shown: true
386+
387+ defaultBackground: shell.background
388+
389+ width: parent.width
390+ height: parent.height
391+
392+ dragHandleWidth: shell.edgeSize
393+
394+ onShownChanged: {
395+ if (shown) {
396+ lockscreen.reset();
397+ // If there is only one user, we start authenticating with that one here.
398+ // If there are more users, the Greeter will handle that
399+ if (LightDM.Users.count == 1) {
400+ LightDM.Greeter.authenticate(LightDM.Users.data(0, LightDM.UserRoles.NameRole));
401+ }
402+ greeter.forceActiveFocus();
388403 }
389− greeter.forceActiveFocus();
390404 }
391− }
392−
393− onUnlocked: greeter.hide()
394− onSelected: {
395− // Update launcher items for new user
396− var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
397− AccountsService.user = user;
398− LauncherModel.setUser(user);
399− }
400405
401− onLeftTeaserPressedChanged: {
402− if (leftTeaserPressed) {
403− launcher.tease();
406+ onUnlocked: greeter.hide()
407+ onSelected: {
408+ // Update launcher items for new user
409+ var user = LightDM.Users.data(uid, LightDM.UserRoles.NameRole);
410+ AccountsService.user = user;
411+ LauncherModel.setUser(user);
404412 }
405− }
406413
407− Binding {
408− target: ApplicationManager
409− property: "suspended"
410− value: greeter.shown && greeter.showProgress == 1
414+ onTease: launcher.tease()
415+
416+ Binding {
417+ target: ApplicationManager
418+ property: "suspended"
419+ value: greeter.shown && greeterWrapper.showProgress == 1
420+ }
411421 }
412422 }
413423
@@ -538,7 +548,7 @@ FocusScope {
538548 anchors.bottom: parent.bottom
539549 width: parent.width
540550 dragAreaWidth: shell.edgeSize
541− available: (!greeter.shown || greeter.narrowMode) && edgeDemo.launcherEnabled
551+ available: edgeDemo.launcherEnabled
542552
543553 onShowDashHome: {
544554 if (edgeDemo.running)
@@ -553,6 +563,10 @@ FocusScope {
553563 launcher.hide();
554564 }
555565 }
566+ if (greeter.shown) {
567+ greeter.hideRight();
568+ launcher.hide();
569+ }
556570 }
557571 onDashSwipeChanged: if (dashSwipe && stages.shown) dash.setCurrentScope("clickscope", false, true)
558572 onLauncherApplicationSelected: {
qml/graphics/dropshadow_left@20.pngnew file

Binary file not shown.

qml/Hud/graphics/dropshadow_right@20.png → qml/graphics/dropshadow_right@20.pngrenamed

Binary file not shown.

tests/qmltests/Greeter/tst_Phone.qml+10−50
@@ -61,6 +61,12 @@ Item {
6161 signalName: "unlocked"
6262 }
6363
64+ SignalSpy {
65+ id: teaseSpy
66+ target: greeter
67+ signalName: "tease"
68+ }
69+
6470 UT.UnityTestCase {
6571 name: "Greeter"
6672 when: windowShown
@@ -82,56 +88,10 @@ Item {
8288 }
8389
8490 function test_teasingArea(data) {
85− tryCompare(greeter, "leftTeaserPressed", false)
86− tryCompare(greeter, "rightTeaserPressed", false)
87− mousePress(greeter, data.posX, greeter.height - units.gu(1))
88− tryCompare(greeter, "leftTeaserPressed", data.leftPressed)
89− tryCompare(greeter, "rightTeaserPressed", data.rightPressed)
90− mouseRelease(greeter, data.posX, greeter.height - units.gu(1))
91− tryCompare(greeter, "leftTeaserPressed", false)
92− tryCompare(greeter, "rightTeaserPressed", false)
93− }
94−
95− function test_teaseLockedUnlocked_data() {
96− return [
97− {tag: "unlocked", locked: false, moved: false},
98− {tag: "locked", locked: true, moved: false},
99− {tag: "moved", locked: false, moved: true}
100− ];
101− }
102−
103− function test_teaseLockedUnlocked(data) {
104− tryCompare(greeter, "rightTeaserPressed", false);
105− tryCompare(greeter, "x", 0);
106− greeter.locked = data.locked;
107−
108− // simulate greeter being in the middle of a swipe
109− if (data.moved) {
110− greeter.x = -units.gu(4);
111− tryCompare(greeter, "x", -units.gu(4));
112− }
113−
114− mouseClick(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
115− greeter.minX = 0; // This is needed because the transition actually makes x jump once before animating
116−
117− if (!data.moved) {
118− // Check if it has been moved over by 2 GUs. Give it a 2 pixel grace area
119− // because animation duration and teaseTimer are the same duration and
120− // might cause slight offsets
121− tryCompareFunction(function() { return greeter.minX <= -units.gu(2) + 2}, true);
122− } else {
123− // waiting 100ms to make sure nothing moves
124− wait(100);
125− compare(greeter.minX, 0, "Greeter teasing not disabled even though it's locked or moving.");
126− }
127−
128− // Restore position in case we moved it for the test
129− if (data.moved) {
130− greeter.x = 0;
131− }
132−
133− // Wait until we're back to 0
134− tryCompareFunction(function() { return greeter.x;}, 0);
91+ teaseSpy.clear()
92+ mouseClick(greeter, data.posX, greeter.height - units.gu(1))
93+ teaseSpy.wait()
94+ tryCompare(teaseSpy, "count", 1)
13595 }
13696
13797 function test_statsWelcomeScreen() {
tests/qmltests/Greeter/tst_Tablet.qml+36−8
@@ -54,6 +54,12 @@ Item {
5454 signalName: "selected"
5555 }
5656
57+ SignalSpy {
58+ id: teaseSpy
59+ target: greeter
60+ signalName: "tease"
61+ }
62+
5763 UT.UnityTestCase {
5864 name: "Greeter"
5965 when: windowShown
@@ -325,14 +331,36 @@ Item {
325331 }
326332
327333 function test_teasingArea(data) {
328− tryCompare(greeter, "leftTeaserPressed", false)
329− tryCompare(greeter, "rightTeaserPressed", false)
330− mousePress(greeter, data.posX, greeter.height - units.gu(1))
331− tryCompare(greeter, "leftTeaserPressed", data.leftPressed)
332− tryCompare(greeter, "rightTeaserPressed", data.rightPressed)
333− mouseRelease(greeter, data.posX, greeter.height - units.gu(1))
334− tryCompare(greeter, "leftTeaserPressed", false)
335− tryCompare(greeter, "rightTeaserPressed", false)
334+ teaseSpy.clear()
335+ mouseClick(greeter, data.posX, greeter.height - units.gu(1))
336+ teaseSpy.wait()
337+ tryCompare(teaseSpy, "count", 1)
338+ }
339+
340+ function test_teaseLockedUnlocked_data() {
341+ return [
342+ {tag: "unlocked", locked: false, narrow: false},
343+ {tag: "locked", locked: true, narrow: false},
344+ ];
345+ }
346+
347+ function test_teaseLockedUnlocked(data) {
348+ teaseSpy.clear()
349+ greeter.locked = data.locked;
350+
351+ mouseClick(greeter, greeter.width - units.gu(5), greeter.height - units.gu(1));
352+
353+ if (!data.locked || data.narrow) {
354+ teaseSpy.wait()
355+ tryCompare(teaseSpy, "count", 1);
356+ } else {
357+ // waiting 100ms to make sure nothing happens
358+ wait(100);
359+ compare(teaseSpy.count, 0, "Greeter teasing not disabled even though it's locked.");
360+ }
361+
362+ // Reset value
363+ greeter.locked = false;
336364 }
337365
338366 function test_dbus_set_active_entry() {