on xenial help sign in
Greeter redesign (#401)
4eca819Capsia
qml/Greeter/ButtonPrompt.qmlnew file+51
@@ -0,0 +1,51 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+ objectName: "promptButton"
8+
9+ property alias text: buttonLabel.text
10+ property alias interactive: root.enabled
11+ property bool isSecret
12+ property bool loginError: false
13+ property bool hasKeyboard: false
14+ property string enteredText: ""
15+
16+ signal clicked()
17+ signal canceled()
18+ signal accepted(string response)
19+
20+ Keys.onSpacePressed: clicked();
21+ Keys.onReturnPressed: clicked();
22+ Keys.onEnterPressed: clicked();
23+
24+ anchors.fill: parent
25+
26+ activeFocusOnTab: true
27+
28+ Rectangle {
29+ anchors.fill: parent
30+ radius: units.gu(0.5)
31+ color: "#7A111111"
32+ Behavior on border.color {
33+ ColorAnimation{}
34+ }
35+ border {
36+ color: root.loginError ? theme.palette.normal.negative : theme.palette.normal.raisedSecondaryText
37+ width: root.loginError ? units.dp(2): units.dp(1)
38+ }
39+ }
40+
41+ MouseArea {
42+ anchors.fill: parent
43+ onClicked: parent.clicked();
44+ }
45+
46+ Label {
47+ id: buttonLabel
48+ anchors.centerIn: parent
49+ color: theme.palette.normal.raisedSecondaryText
50+ }
51+}
qml/Greeter/CoverPage.qml+29
@@ -16,6 +16,7 @@
16 */
16 */
17
17
18import QtQuick 2.4
18import QtQuick 2.4
19+import QtGraphicalEffects 1.12
19import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
20import Ubuntu.Gestures 0.1
21import Ubuntu.Gestures 0.1
21import "../Components"
22import "../Components"
@@ -34,6 +35,11 @@ Showable {
34
35
35 property alias infographics: infographics
36 property alias infographics: infographics
36
37
38+ property alias blurAreaHeight: loginBoxEffects.height
39+ property alias blurAreaWidth: loginBoxEffects.width
40+ property alias blurAreaX: loginBoxEffects.x
41+ property alias blurAreaY: loginBoxEffects.y
42+
37 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
43 readonly property real showProgress: MathUtils.clamp((width - Math.abs(x + launcherOffset)) / width, 0, 1)
38
44
39 signal tease()
45 signal tease()
@@ -93,6 +99,29 @@ Showable {
93 }
99 }
94 }
100 }
95
101
102+ Rectangle {
103+ id: loginBoxEffects
104+ color: "transparent"
105+ }
106+
107+ ShaderEffectSource {
108+ id: effectSource
109+
110+ sourceItem: greeterBackground
111+ anchors.centerIn: loginBoxEffects
112+ width: loginBoxEffects.width
113+ height: loginBoxEffects.height
114+ sourceRect: Qt.rect(x,y, width, height)
115+ }
116+
117+ FastBlur {
118+ visible: !draggable
119+ anchors.fill: effectSource
120+ source: effectSource
121+ radius: 64
122+ transparentBorder: true
123+ }
124+
96 // Darkens wallpaper so that we can read text on it and see infographic
125 // Darkens wallpaper so that we can read text on it and see infographic
97 Rectangle {
126 Rectangle {
98 id: backgroundShade
127 id: backgroundShade
qml/Greeter/Greeter.qml+16
@@ -66,6 +66,10 @@ Showable {
66
66
67 readonly property bool animating: loader.item ? loader.item.animating : false
67 readonly property bool animating: loader.item ? loader.item.animating : false
68
68
69+ property rect inputMethodRect
70+
71+ property bool hasKeyboard: false
72+
69 signal tease()
73 signal tease()
70 signal sessionStarted()
74 signal sessionStarted()
71 signal emergencyCall()
75 signal emergencyCall()
@@ -460,6 +464,18 @@ Showable {
460 property: "infographicModel"
464 property: "infographicModel"
461 value: LightDMService.infographic
465 value: LightDMService.infographic
462 }
466 }
467+
468+ Binding {
469+ target: loader.item
470+ property: "inputMethodRect"
471+ value: root.inputMethodRect
472+ }
473+
474+ Binding {
475+ target: loader.item
476+ property: "hasKeyboard"
477+ value: root.hasKeyboard
478+ }
463 }
479 }
464
480
465 Connections {
481 Connections {
qml/Greeter/GreeterPrompt.qml+63−186
@@ -29,222 +29,99 @@ FocusScope {
29 property string text
29 property string text
30 property bool isSecret
30 property bool isSecret
31 property bool interactive: true
31 property bool interactive: true
32+ property bool loginError: false
32− readonly property alias enteredText: passwordInput.text
33+ readonly property string enteredText: loader.item.enteredText
34+ property bool hasKeyboard: false
35+ property bool waitingToAccept: false
33
36
34 signal clicked()
37 signal clicked()
35 signal canceled()
38 signal canceled()
36 signal accepted()
39 signal accepted()
37
40
38− function showFakePassword() {
39− // Just a silly hack for looking like 4 pin numbers got entered, if
40− // a fingerprint was used and we happen to be using a pin. This was
41− // a request from Design.
42− if (isSecret && isPrompt && !isAlphanumeric) {
43− passwordInput.text = "...."; // actual text doesn't matter
44− }
45− }
46−
47 GSettings {
41 GSettings {
48 id: unity8Settings
42 id: unity8Settings
49 schema.id: "com.canonical.Unity8"
43 schema.id: "com.canonical.Unity8"
50 }
44 }
51
45
52− StyledItem {
46+ onEnteredTextChanged: if (waitingToAccept) root.accepted()
53− id: d
54−
55− readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56− : theme.palette.disabled.raisedText
57− readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
58− : theme.palette.disabled.raised
59− readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
60− : theme.palette.disabled.raisedSecondaryText
61− readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62− : theme.palette.disabled.negative
63− }
64
47
65− Rectangle {
48+ function showFakePassword() {
66− anchors.fill: parent
49+ // Just a silly hack for looking like 4 pin numbers got entered, if
67− border.width: units.dp(1)
50+ // a fingerprint was used and we happen to be using a pin. This was
68− border.color: d.drawColor
51+ // a request from Design.
69− radius: units.gu(0.5)
52+ if (isSecret) {
70− color: "transparent"
53+ loader.item.enteredText = "...."; // actual text doesn't matter
71− }
72−
73− Component.onCompleted: updateFocus()
74− onIsPromptChanged: updateFocus()
75− function updateFocus() {
76− if (root.isPrompt) {
77− passwordInput.focus = true;
78− } else {
79− promptButton.focus = true;
80 }
54 }
81 }
55 }
82
56
83− StyledItem {
57+ Loader {
84− id: promptButton
58+ id: loader
85− objectName: "promptButton"
59+ objectName: "promptLoader"
86− anchors.fill: parent
87− visible: !root.isPrompt
88− activeFocusOnTab: true
89
60
90− styleName: "FocusShape"
61+ focus: true
91
62
92− function triggered() {
63+ anchors.fill: parent
93− if (root.interactive) {
94− root.clicked();
95− }
96− }
97
64
98− Rectangle {
65+ Connections {
99− height: parent.height;
66+ target: loader.item
100− width: parent.width
67+ onClicked: root.clicked()
101− color: "transparent"
68+ onCanceled: root.canceled()
102− border {
69+ onAccepted: {
103− color: d.textColor
70+ if (response == enteredText)
104− width: units.dp(1)
71+ root.accepted();
72+ else
73+ waitingToAccept = true;
105 }
74 }
106 }
75 }
107
76
108− Keys.onSpacePressed: triggered();
77+ Binding {
109− Keys.onReturnPressed: triggered();
78+ target: loader.item
110− Keys.onEnterPressed: triggered();
79+ property: "text"
111− MouseArea {
80+ value: root.text
112− anchors.fill: parent
113− onClicked: parent.triggered();
114 }
81 }
115
82
116− Label {
83+ Binding {
117− anchors.centerIn: parent
84+ target: loader.item
118− color: d.textColor
85+ property: "isSecret"
119− text: root.text
86+ value: root.isSecret
120 }
87 }
121− }
122−
123− TextField {
124− id: passwordInput
125− objectName: "promptField"
126− anchors.fill: parent
127− visible: root.isPrompt
128− opacity: fakeLabel.visible ? 0 : 1
129− activeFocusOnTab: true
130
88
131− validator: RegExpValidator {
89+ Binding {
132− regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
90+ target: loader.item
91+ property: "interactive"
92+ value: root.interactive
133 }
93 }
134
94
135− inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
95+ Binding {
136− Qt.ImhMultiLine | // so OSK doesn't close on Enter
96+ target: loader.item
137− (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
97+ property: "loginError"
138− echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
98+ value: root.loginError
139− hasClearButton: false
140−
141− readonly property real frameSpacing: units.gu(0.5)
142−
143− style: StyledItem {
144− anchors.fill: parent
145− styleName: "FocusShape"
146−
147− // Properties needed by TextField
148− readonly property color color: d.textColor
149− readonly property color selectedTextColor: d.selectedColor
150− readonly property color selectionColor: d.textColor
151− readonly property color borderColor: "transparent"
152− readonly property color backgroundColor: "transparent"
153− readonly property color errorColor: d.errorColor
154− readonly property real frameSpacing: styledItem.frameSpacing
155−
156− // Properties needed by FocusShape
157− readonly property bool enabled: styledItem.enabled
158− readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
159− property bool activeFocusOnTab
160 }
99 }
161
100
162− secondaryItem: [
101+ Binding {
163− Row {
102+ target: loader.item
164− id: extraIcons
103+ property: "hasKeyboard"
165− spacing: passwordInput.frameSpacing
104+ value: root.hasKeyboard
166− Icon {
167− name: "keyboard-caps-enabled"
168− height: units.gu(3)
169− width: units.gu(3)
170− color: d.textColor
171− visible: root.isSecret && false // TODO: detect when caps lock is on
172− }
173− Icon {
174− name: "input-keyboard-symbolic"
175− height: units.gu(3)
176− width: units.gu(3)
177− color: d.textColor
178− visible: !unity8Settings.alwaysShowOsk
179− MouseArea {
180− anchors.fill: parent
181− onClicked: unity8Settings.alwaysShowOsk = true
182− }
183− }
184− }
185− ]
186−
187− onDisplayTextChanged: {
188− // We use onDisplayTextChanged instead of onTextChanged because
189− // displayText changes after text and if we did this before it
190− // updated, we would use the wrong displayText for fakeLabel.
191− if (!isAlphanumeric && text.length >= 4) {
192− // hard limit of 4 for passcodes right now
193− respond();
194− }
195 }
105 }
196
106
197− onAccepted: respond()
107+ onLoaded: loader.item.focus = true
198−
199− function respond() {
200− if (root.interactive) {
201− root.accepted();
202− }
203− }
204−
205− Keys.onEscapePressed: {
206− root.canceled();
207− event.accepted = true;
208− }
209−
210− // We use our own custom placeholder label instead of the standard
211− // TextField one because the standard one hardcodes baseText as the
212− // palette color, whereas we want raisedSecondaryText.
213− Label {
214− id: hint
215− objectName: "promptHint"
216− anchors {
217− left: parent ? parent.left : undefined
218− right: parent ? parent.right : undefined
219− verticalCenter: parent ? parent.verticalCenter : undefined
220− leftMargin: units.gu(1.5)
221− rightMargin: anchors.leftMargin + extraIcons.width
222− }
223− text: root.text
224− visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
225− enabled: visible
226− color: d.drawColor
227− elide: Text.ElideRight
228− }
229 }
108 }
230
109
231− // Have a fake label that covers the text field after the user presses
110+ states: [
232− // enter. What we *really* want is a disabled mode that doesn't lose OSK
111+ State {
233− // focus. Because our goal here is simply to keep the OSK up while
112+ name: "ButtonPrompt"
234− // we wait for PAM to get back to us, and while waiting, we don't want
113+ when: !root.isPrompt
235− // the user to be able to edit the field (simply because it would look
114+ PropertyChanges { target: loader; source: "ButtonPrompt.qml" }
236− // weird if we allowed that). But until we have such a disabled mode,
115+ },
237− // we'll fake it by covering the real text field with a label.
116+ State {
238− FadingLabel {
117+ name: "PinPrompt"
239− id: fakeLabel
118+ when: root.isPrompt && !root.isAlphanumeric && root.isSecret
240− anchors.verticalCenter: parent ? parent.verticalCenter : undefined
119+ PropertyChanges { target: loader; source: "PinPrompt.qml" }
241− anchors.left: parent ? parent.left : undefined
120+ },
242− anchors.right: parent ? parent.right : undefined
121+ State {
243− anchors.leftMargin: passwordInput.frameSpacing * 2
122+ name: "TextPrompt"
244− anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
245− color: d.drawColor
246− text: passwordInput.displayText
247− visible: root.isPrompt && !root.interactive
123+ when: root.isPrompt
248− enabled: visible
124+ PropertyChanges { target: loader; source: "TextPrompt.qml" }
249− }
125+ }
126+ ]
250}
127}
qml/Greeter/LoginAreaContainer.qml+3−3
@@ -21,18 +21,18 @@ Item {
21 BorderImage {
21 BorderImage {
22 anchors {
22 anchors {
23 fill: parent
23 fill: parent
24− topMargin: -units.gu(1)
24+ topMargin: -units.gu(3)
25 leftMargin: -units.gu(1.5)
25 leftMargin: -units.gu(1.5)
26 rightMargin: -units.gu(1.5)
26 rightMargin: -units.gu(1.5)
27 bottomMargin: -units.gu(1.5)
27 bottomMargin: -units.gu(1.5)
28 }
28 }
29 source: "../graphics/dropshadow2gu.sci"
29 source: "../graphics/dropshadow2gu.sci"
30− opacity: 0.35
30+ opacity: 0
31 }
31 }
32
32
33 UbuntuShape {
33 UbuntuShape {
34 anchors.fill: parent
34 anchors.fill: parent
35 aspect: UbuntuShape.Flat
35 aspect: UbuntuShape.Flat
36− backgroundColor: theme.palette.normal.raised
36+ backgroundColor: "transparent"
37 }
37 }
38}
38}
qml/Greeter/LoginList.qml+68−97
@@ -26,6 +26,7 @@ StyledItem {
26
26
27 property alias model: userList.model
27 property alias model: userList.model
28 property alias alphanumeric: promptList.alphanumeric
28 property alias alphanumeric: promptList.alphanumeric
29+ property alias hasKeyboard: promptList.hasKeyboard
29 property int currentIndex
30 property int currentIndex
30 property bool locked
31 property bool locked
31 property bool waiting
32 property bool waiting
@@ -39,6 +40,8 @@ StyledItem {
39 property string currentSession // Initially set by LightDM
40 property string currentSession // Initially set by LightDM
40 readonly property string currentUser: userList.currentItem.username
41 readonly property string currentUser: userList.currentItem.username
41
42
43+ readonly property alias currentUserIndex: userList.currentIndex
44+
42 signal responded(string response)
45 signal responded(string response)
43 signal selected(int index)
46 signal selected(int index)
44 signal sessionChooserButtonClicked()
47 signal sessionChooserButtonClicked()
@@ -48,6 +51,7 @@ StyledItem {
48 }
51 }
49
52
50 function showError() {
53 function showError() {
54+ promptList.loginError = true;
51 wrongPasswordAnimation.start();
55 wrongPasswordAnimation.start();
52 }
56 }
53
57
@@ -79,6 +83,7 @@ StyledItem {
79
83
80 onCurrentIndexChanged: {
84 onCurrentIndexChanged: {
81 userList.currentIndex = currentIndex;
85 userList.currentIndex = currentIndex;
86+ promptList.loginError = false;
82 }
87 }
83
88
84 LoginAreaContainer {
89 LoginAreaContainer {
@@ -93,18 +98,6 @@ StyledItem {
93
98
94 height: Math.max(units.gu(15), promptList.height + units.gu(8))
99 height: Math.max(units.gu(15), promptList.height + units.gu(8))
95 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
100 Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
96−
97− Label {
98− // HACK: Work around https://github.com/ubports/unity8/issues/185
99− text: _realName ? _realName : LightDMService.greeter.authenticationUser
100− visible: userList.count == 1
101− anchors {
102− left: parent.left
103− top: parent.top
104− topMargin: units.gu(2)
105− leftMargin: units.gu(2)
106− }
107− }
108 }
101 }
109
102
110 ListView {
103 ListView {
@@ -115,14 +108,16 @@ StyledItem {
115 anchors.leftMargin: units.gu(2)
108 anchors.leftMargin: units.gu(2)
116 anchors.rightMargin: units.gu(2)
109 anchors.rightMargin: units.gu(2)
117
110
118− preferredHighlightBegin: highlightItem.y
111+ preferredHighlightBegin: highlightItem.y + units.gu(1.5)
119− preferredHighlightEnd: highlightItem.y
112+ preferredHighlightEnd: highlightItem.y + units.gu(1.5)
120 highlightRangeMode: ListView.StrictlyEnforceRange
113 highlightRangeMode: ListView.StrictlyEnforceRange
121 highlightMoveDuration: root.moveDuration
114 highlightMoveDuration: root.moveDuration
122 interactive: count > 1
115 interactive: count > 1
123
116
124 readonly property bool movingInternally: moveTimer.running || userList.moving
117 readonly property bool movingInternally: moveTimer.running || userList.moving
125
118
119+ onMovingChanged: if (!moving) root.selected(currentIndex)
120+
126 onCurrentIndexChanged: {
121 onCurrentIndexChanged: {
127 moveTimer.start();
122 moveTimer.start();
128 }
123 }
@@ -132,6 +127,7 @@ StyledItem {
132 height: root.cellHeight
127 height: root.cellHeight
133
128
134 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
129 readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
130+ readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
135 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
131 readonly property int belowOffset: root.highlightedHeight - root.cellHeight
136 readonly property string userSession: session
132 readonly property string userSession: session
137 readonly property string username: name
133 readonly property string username: name
@@ -154,41 +150,75 @@ StyledItem {
154 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
150 return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
155 }
151 }
156
152
157− FadingLabel {
153+ Row {
158− objectName: "username" + index
154+ spacing: units.gu(1)
159− visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
155+// visible: userList.count != 1 // HACK Hide username label until someone sorts out the anchoring with the keyboard-dismiss animation, Work around https://github.com/ubports/unity8/issues/185
160
156
161 anchors {
157 anchors {
162− left: parent.left
163 leftMargin: units.gu(2)
158 leftMargin: units.gu(2)
164− right: parent.right
165 rightMargin: units.gu(2)
159 rightMargin: units.gu(2)
160+ horizontalCenter: parent.horizontalCenter
166 bottom: parent.top
161 bottom: parent.top
167 // Add an offset to bottomMargin for any items below the highlight
162 // Add an offset to bottomMargin for any items below the highlight
168− bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : 0))
163+ bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
169 }
164 }
170− text: userList.currentIndex === index
171− && name === "*other"
172− && LightDMService.greeter.authenticationUser !== ""
173− ? LightDMService.greeter.authenticationUser : realName
174− color: userList.currentIndex !== index ? theme.palette.normal.raised
175− : theme.palette.normal.raisedText
176−
177− Component.onCompleted: _realName = realName
178−
179− Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
180
165
181 Rectangle {
166 Rectangle {
182 id: activeIndicator
167 id: activeIndicator
183− anchors.horizontalCenter: parent.left
184− anchors.horizontalCenterOffset: -units.gu(1)
185 anchors.verticalCenter: parent.verticalCenter
168 anchors.verticalCenter: parent.verticalCenter
186− color: userList.currentIndex !== index ? theme.palette.normal.raised
169+ color: theme.palette.normal.raised
187− : theme.palette.normal.focus
188 visible: userList.count > 1 && loggedIn
170 visible: userList.count > 1 && loggedIn
189− height: units.gu(0.5)
171+ height: visible ? units.gu(0.5) : 0
190 width: height
172 width: height
191 }
173 }
174+
175+ Icon {
176+ id: userIcon
177+ name: "account"
178+ height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
179+ width: height
180+ color: theme.palette.normal.raisedSecondaryText
181+ anchors.verticalCenter: parent.verticalCenter
182+ }
183+
184+ Column {
185+ anchors.verticalCenter: parent.verticalCenter
186+ spacing: units.gu(0.25)
187+
188+ FadingLabel {
189+ objectName: "username" + index
190+
191+ text: userList.currentIndex === index
192+ && name === "*other"
193+ && LightDMService.greeter.authenticationUser !== ""
194+ ? LightDMService.greeter.authenticationUser : realName
195+ color: userList.currentIndex !== index ? theme.palette.normal.raised
196+ : theme.palette.normal.raisedSecondaryText
197+ font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
198+ font.pointSize: units.gu(2)
199+
200+ width: highlightItem.width
201+ && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
202+ ? highlightItem.width - userIcon.width - units.gu(4)
203+ : contentWidth
204+
205+ Component.onCompleted: _realName = realName
206+
207+ Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
208+ }
209+
210+ Row {
211+ spacing: units.gu(1)
212+
213+ FadingLabel {
214+ text: root.alphanumeric ? "Login with password" : "Login with pin"
215+ color: theme.palette.normal.raisedSecondaryText
216+ visible: userList.currentIndex === index && false
217+ font.weight: Font.Light
218+ font.pointSize: units.gu(1.25)
219+ }
220+ }
221+ }
192 }
222 }
193
223
194 MouseArea {
224 MouseArea {
@@ -197,7 +227,7 @@ StyledItem {
197 right: parent.right
227 right: parent.right
198 top: parent.top
228 top: parent.top
199 // Add an offset to topMargin for any items below the highlight
229 // Add an offset to topMargin for any items below the highlight
200− topMargin: parent.belowHighlight ? parent.belowOffset : 0
230+ topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
201 }
231 }
202 height: parent.height
232 height: parent.height
203 enabled: userList.currentIndex !== index && parent.opacity > 0
233 enabled: userList.currentIndex !== index && parent.opacity > 0
@@ -217,67 +247,6 @@ StyledItem {
217 }
247 }
218 }
248 }
219
249
220− // Use an AbstractButton due to icon limitations with Button
221− AbstractButton {
222− id: sessionChooser
223− objectName: "sessionChooserButton"
224−
225− readonly property alias icon: badge.source
226−
227− visible: LightDMService.sessions.count > 1 &&
228− !LightDMService.users.data(userList.currentIndex, LightDMService.userRoles.LoggedInRole)
229−
230− height: units.gu(3.5)
231− width: units.gu(3.5)
232−
233− activeFocusOnTab: true
234− anchors {
235− right: highlightItem.right
236− rightMargin: units.gu(2)
237−
238− top: highlightItem.top
239− topMargin: units.gu(1.5)
240− }
241−
242− Rectangle {
243− id: badgeHighlight
244−
245− anchors.fill: parent
246− visible: parent.activeFocus
247− color: "transparent"
248− border.color: theme.palette.normal.focus
249− border.width: units.dp(1)
250− radius: 3
251− }
252−
253− Icon {
254− id: badge
255− anchors.fill: parent
256− anchors.margins: units.dp(3)
257− keyColor: "#ffffff" // icon providers give us white icons
258− color: theme.palette.normal.raisedSecondaryText
259− source: LightDMService.sessions.iconUrl(root.currentSession)
260− }
261−
262− Keys.onReturnPressed: {
263− sessionChooserButtonClicked();
264− event.accepted = true;
265− }
266−
267− onClicked: {
268− sessionChooserButtonClicked();
269− }
270−
271− // Refresh the icon path if looking at different places at runtime
272− // this is mainly for testing
273− Connections {
274− target: LightDMService.sessions
275− onIconSearchDirectoriesChanged: {
276− badge.source = LightDMService.sessions.iconUrl(root.currentSession)
277− }
278− }
279− }
280−
281 PromptList {
250 PromptList {
282 id: promptList
251 id: promptList
283 objectName: "promptList"
252 objectName: "promptList"
@@ -288,6 +257,8 @@ StyledItem {
288 }
257 }
289 width: highlightItem.width - anchors.margins * 2
258 width: highlightItem.width - anchors.margins * 2
290
259
260+ focus: true
261+
291 onClicked: {
262 onClicked: {
292 interactive = false;
263 interactive = false;
293 if (root.locked) {
264 if (root.locked) {
qml/Greeter/NarrowView.qml+9−6
@@ -17,6 +17,7 @@
17
17
18import QtQuick 2.4
18import QtQuick 2.4
19import QtQuick.Window 2.2
19import QtQuick.Window 2.2
20+import QtGraphicalEffects 1.12
20import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
21import Ubuntu.Telephony 0.1 as Telephony
22import Ubuntu.Telephony 0.1 as Telephony
22import "../Components"
23import "../Components"
@@ -34,6 +35,7 @@ FocusScope {
34 property bool hasCustomBackground
35 property bool hasCustomBackground
35 property bool locked
36 property bool locked
36 property alias alphanumeric: loginList.alphanumeric
37 property alias alphanumeric: loginList.alphanumeric
38+ property alias hasKeyboard: loginList.hasKeyboard
37 property alias userModel: loginList.model
39 property alias userModel: loginList.model
38 property alias infographicModel: coverPage.infographicModel
40 property alias infographicModel: coverPage.infographicModel
39 property string sessionToStart
41 property string sessionToStart
@@ -42,8 +44,7 @@ FocusScope {
42 readonly property bool required: coverPage.required || lockscreen.required
44 readonly property bool required: coverPage.required || lockscreen.required
43 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
45 readonly property bool animating: coverPage.showAnimation.running || coverPage.hideAnimation.running
44
46
45− // so that it can be replaced in tests with a mock object
47+ property rect inputMethodRect
46− property var inputMethod: Qt.inputMethod
47
48
48 signal selected(int index)
49 signal selected(int index)
49 signal responded(string response)
50 signal responded(string response)
@@ -90,6 +91,10 @@ FocusScope {
90 }
91 }
91 }
92 }
92
93
94+ Keys.onSpacePressed: coverPage.hide();
95+ Keys.onReturnPressed: coverPage.hide();
96+ Keys.onEnterPressed: coverPage.hide();
97+
93 Showable {
98 Showable {
94 id: lockscreen
99 id: lockscreen
95 objectName: "lockscreen"
100 objectName: "lockscreen"
@@ -207,8 +212,7 @@ FocusScope {
207 anchors.right: parent.right
212 anchors.right: parent.right
208 anchors.top: parent.bottom
213 anchors.top: parent.bottom
209 anchors.topMargin: - height * (1 - coverPage.showProgress)
214 anchors.topMargin: - height * (1 - coverPage.showProgress)
210− - (inputMethod && inputMethod.visible ?
215+ - ( inputMethodRect.height )
211− inputMethod.keyboardRectangle.height : 0)
212
216
213 Rectangle {
217 Rectangle {
214 color: UbuntuColors.porcelain // matches OSK background
218 color: UbuntuColors.porcelain // matches OSK background
@@ -264,8 +268,7 @@ FocusScope {
264 // during OSK animations.
268 // during OSK animations.
265 Rectangle {
269 Rectangle {
266 visible: bottomBar.visible
270 visible: bottomBar.visible
267− height: inputMethod && inputMethod.visible ?
271+ height: inputMethodRect.height
268− inputMethod.keyboardRectangle.height : 0
269 anchors.bottom: parent.bottom
272 anchors.bottom: parent.bottom
270 anchors.left: parent.left
273 anchors.left: parent.left
271 anchors.right: parent.right
274 anchors.right: parent.right
qml/Greeter/PinPrompt.qmlnew file+184
@@ -0,0 +1,184 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ TextField {
33+ id: passwordInput
34+ objectName: "promptField"
35+ anchors.left: extraIcons.left
36+ focus: root.focus
37+
38+ opacity: fakeLabel.visible ? 0 : 1
39+ activeFocusOnTab: true
40+
41+ onSelectedTextChanged: passwordInput.deselect()
42+ onCursorPositionChanged: cursorPosition = length
43+
44+ validator: RegExpValidator {
45+ regExp: /^\d{4}$/
46+ }
47+
48+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
49+ Qt.ImhMultiLine | // so OSK doesn't close on Enter
50+ Qt.ImhDigitsOnly
51+ echoMode: TextInput.Password
52+ hasClearButton: false
53+
54+ cursorDelegate: Item {}
55+
56+ passwordCharacter: "●"
57+ color: d.drawColor
58+
59+ readonly property real letterSpacing: units.gu(1.75)
60+ readonly property real frameSpacing: letterSpacing
61+
62+ font.pixelSize: units.gu(3)
63+ font.letterSpacing: letterSpacing
64+
65+ style: StyledItem {
66+ anchors.fill: parent
67+ styleName: "FocusShape"
68+
69+ // Properties needed by TextField
70+ readonly property color color: d.textColor
71+ readonly property color selectedTextColor: d.selectedColor
72+ readonly property color selectionColor: d.textColor
73+ readonly property color borderColor: "transparent"
74+ readonly property color backgroundColor: "transparent"
75+ readonly property color errorColor: d.errorColor
76+ readonly property real frameSpacing: 0
77+
78+ // Properties needed by FocusShape
79+ readonly property bool enabled: styledItem.enabled
80+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
81+ property bool activeFocusOnTab
82+ }
83+
84+ onDisplayTextChanged: {
85+ // We use onDisplayTextChanged instead of onTextChanged because
86+ // displayText changes after text and if we did this before it
87+ // updated, we would use the wrong displayText for fakeLabel.
88+ root.loginError = false;
89+ if (text.length >= 4) {
90+ // hard limit of 4 for passcodes right now
91+ respond();
92+ }
93+ }
94+
95+ onAccepted: respond()
96+
97+ function respond() {
98+ if (root.interactive) {
99+ root.accepted(passwordInput.text);
100+ }
101+ }
102+
103+ Keys.onEscapePressed: {
104+ root.canceled();
105+ event.accepted = true;
106+ }
107+ }
108+
109+ Row {
110+ id: extraIcons
111+ spacing: passwordInput.frameSpacing
112+ anchors {
113+ horizontalCenter: parent ? parent.horizontalCenter : undefined
114+ horizontalCenterOffset: passwordInput.letterSpacing / 2
115+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
116+ }
117+
118+ Label {
119+ id: pinHint
120+ objectName: "promptPinHint"
121+
122+ text: "○○○○"
123+ enabled: visible
124+ color: d.drawColor
125+ font {
126+ pixelSize: units.gu(3)
127+ letterSpacing: units.gu(1.75)
128+ }
129+ elide: Text.ElideRight
130+ }
131+ Icon {
132+ name: "keyboard-caps-enabled"
133+ height: units.gu(3)
134+ width: height
135+ color: d.drawColor
136+ visible: false // TODO: detect when caps lock is on
137+ anchors.verticalCenter: parent.verticalCenter
138+ }
139+ Icon {
140+ objectName: "greeterPromptKeyboardButton"
141+ name: "input-keyboard-symbolic"
142+ height: units.gu(3)
143+ width: height
144+ color: d.drawColor
145+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
146+ anchors.verticalCenter: parent.verticalCenter
147+ MouseArea {
148+ anchors.fill: parent
149+ onClicked: unity8Settings.alwaysShowOsk = true
150+ }
151+ }
152+ Icon {
153+ name: "dialog-warning-symbolic"
154+ height: units.gu(3)
155+ width: height
156+ color: d.drawColor
157+ visible: root.loginError
158+ anchors.verticalCenter: parent.verticalCenter
159+ }
160+ }
161+
162+ // Have a fake label that covers the text field after the user presses
163+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
164+ // focus. Because our goal here is simply to keep the OSK up while
165+ // we wait for PAM to get back to us, and while waiting, we don't want
166+ // the user to be able to edit the field (simply because it would look
167+ // weird if we allowed that). But until we have such a disabled mode,
168+ // we'll fake it by covering the real text field with a label.
169+ FadingLabel {
170+ id: fakeLabel
171+ anchors.verticalCenter: extraIcons ? extraIcons.verticalCenter : undefined
172+ anchors.left: extraIcons ? extraIcons.left : undefined
173+ anchors.right: parent ? parent.right : undefined
174+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
175+ color: d.drawColor
176+ font {
177+ pixelSize: pinHint.font.pixelSize
178+ letterSpacing: pinHint.font.letterSpacing
179+ }
180+ text: passwordInput.displayText
181+ visible: !root.interactive
182+ enabled: visible
183+ }
184+}
qml/Greeter/PromptList.qml+7−1
@@ -25,6 +25,8 @@ FocusScope {
25
25
26 property bool alphanumeric: true
26 property bool alphanumeric: true
27 property bool interactive: true
27 property bool interactive: true
28+ property bool loginError: false
29+ property bool hasKeyboard: false
28
30
29 signal responded(string text)
31 signal responded(string text)
30 signal clicked()
32 signal clicked()
@@ -99,12 +101,14 @@ FocusScope {
99 property var model
101 property var model
100 readonly property bool isPrompt: false
102 readonly property bool isPrompt: false
101
103
102− color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedText
104+ color: model.type === LightDMService.prompts.Message ? theme.palette.normal.raisedSecondaryText
103 : theme.palette.normal.negative
105 : theme.palette.normal.negative
104 fontSize: "small"
106 fontSize: "small"
105 textFormat: Text.PlainText
107 textFormat: Text.PlainText
106 text: model.text
108 text: model.text
107
109
110+ visible: model.type === LightDMService.prompts.Message
111+
108 Behavior on opacity { UbuntuNumberAnimation {} }
112 Behavior on opacity { UbuntuNumberAnimation {} }
109 opacity: 0
113 opacity: 0
110 }
114 }
@@ -123,6 +127,8 @@ FocusScope {
123 isAlphanumeric: model.text !== "" || root.alphanumeric
127 isAlphanumeric: model.text !== "" || root.alphanumeric
124 isPrompt: model.type !== LightDMService.prompts.Button
128 isPrompt: model.type !== LightDMService.prompts.Button
125 isSecret: model.type === LightDMService.prompts.Secret
129 isSecret: model.type === LightDMService.prompts.Secret
130+ loginError: root.loginError
131+ hasKeyboard: root.hasKeyboard
126 text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
132 text: model.text ? model.text : (isAlphanumeric ? i18n.tr("Passphrase") : i18n.tr("Passcode"))
127
133
128 onClicked: root.clicked()
134 onClicked: root.clicked()
qml/Greeter/SessionsList.qml+3−2
@@ -119,13 +119,14 @@ Item {
119
119
120 padding.leading: 0 // handled by parent's margins
120 padding.leading: 0 // handled by parent's margins
121
121
122− title.color: theme.palette.normal.raisedText
122+ title.color: theme.palette.normal.raisedSecondaryText
123 title.font.pixelSize: units.gu(2.1)
123 title.font.pixelSize: units.gu(2.1)
124 title.text: i18n.tr("Select desktop environment")
124 title.text: i18n.tr("Select desktop environment")
125
125
126 Icon {
126 Icon {
127 id: icon
127 id: icon
128 width: units.gu(3)
128 width: units.gu(3)
129+ color: theme.palette.normal.raisedSecondaryText
129 SlotsLayout.position: SlotsLayout.Leading
130 SlotsLayout.position: SlotsLayout.Leading
130 name: "go-previous"
131 name: "go-previous"
131
132
@@ -187,7 +188,7 @@ Item {
187 ListItemLayout {
188 ListItemLayout {
188 id: layout
189 id: layout
189
190
190− readonly property color itemColor: theme.palette.normal.raisedText
191+ readonly property color itemColor: theme.palette.normal.raisedSecondaryText
191 SessionIcon {
192 SessionIcon {
192 id: sessionIcon
193 id: sessionIcon
193 source: icon_url
194 source: icon_url
qml/Greeter/TextPrompt.qmlnew file+198
@@ -0,0 +1,198 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import "../Components"
4+
5+FocusScope {
6+ id: root
7+
8+ property string text
9+ property bool isSecret
10+ property bool interactive: true
11+ property bool loginError: false
12+ property bool hasKeyboard: false
13+ property alias enteredText: passwordInput.text
14+
15+ signal clicked()
16+ signal canceled()
17+ signal accepted(string response)
18+
19+ StyledItem {
20+ id: d
21+
22+ readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
23+ : theme.palette.disabled.raisedText
24+ readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
25+ : theme.palette.disabled.raised
26+ readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
27+ : theme.palette.disabled.raisedSecondaryText
28+ readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
29+ : theme.palette.disabled.negative
30+ }
31+
32+ Rectangle {
33+ anchors.fill: parent
34+ radius: units.gu(0.5)
35+ color: "#7A111111"
36+ Behavior on border.color {
37+ ColorAnimation{}
38+ }
39+ border {
40+ color: root.loginError ? d.errorColor : d.drawColor
41+ width: root.loginError ? units.dp(2): units.dp(1)
42+ }
43+ }
44+
45+ TextField {
46+ id: passwordInput
47+ objectName: "promptField"
48+ anchors.fill: parent
49+ focus: root.focus
50+
51+ opacity: fakeLabel.visible ? 0 : 1
52+ activeFocusOnTab: true
53+
54+ onSelectedTextChanged: passwordInput.deselect()
55+
56+ validator: RegExpValidator {
57+ regExp: /^.*$/
58+ }
59+
60+ inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
61+ Qt.ImhMultiLine // so OSK doesn't close on Enter
62+ echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
63+ hasClearButton: false
64+
65+ passwordCharacter: "●"
66+ color: d.drawColor
67+
68+ readonly property real frameSpacing: units.gu(1)
69+
70+ style: StyledItem {
71+ anchors.fill: parent
72+ styleName: "FocusShape"
73+
74+ // Properties needed by TextField
75+ readonly property color color: d.textColor
76+ readonly property color selectedTextColor: d.selectedColor
77+ readonly property color selectionColor: d.textColor
78+ readonly property color borderColor: "transparent"
79+ readonly property color backgroundColor: "transparent"
80+ readonly property color errorColor: d.errorColor
81+ readonly property real frameSpacing: styledItem.frameSpacing
82+
83+ // Properties needed by FocusShape
84+ readonly property bool enabled: styledItem.enabled
85+ readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
86+ property bool activeFocusOnTab
87+ }
88+
89+ secondaryItem: [
90+ Row {
91+ id: extraIcons
92+ spacing: passwordInput.frameSpacing
93+ anchors.verticalCenter: parent.verticalCenter
94+ Icon {
95+ name: "keyboard-caps-enabled"
96+ height: units.gu(3)
97+ width: units.gu(3)
98+ color: d.drawColor
99+ visible: root.isSecret && false // TODO: detect when caps lock is on
100+ anchors.verticalCenter: parent.verticalCenter
101+ }
102+ Icon {
103+ objectName: "greeterPromptKeyboardButton"
104+ name: "input-keyboard-symbolic"
105+ height: units.gu(3)
106+ width: units.gu(3)
107+ color: d.drawColor
108+ visible: !unity8Settings.alwaysShowOsk && root.hasKeyboard
109+ anchors.verticalCenter: parent.verticalCenter
110+ MouseArea {
111+ anchors.fill: parent
112+ onClicked: unity8Settings.alwaysShowOsk = true
113+ }
114+ }
115+ Icon {
116+ name: "dialog-warning-symbolic"
117+ height: units.gu(3)
118+ width: units.gu(3)
119+ color: d.drawColor
120+ visible: root.loginError
121+ anchors.verticalCenter: parent.verticalCenter
122+ }
123+ Icon {
124+ name: "toolkit_chevron-ltr_2gu"
125+ height: units.gu(2.5)
126+ width: units.gu(2.5)
127+ color: d.drawColor
128+ visible: !root.loginError
129+ anchors.verticalCenter: parent.verticalCenter
130+ MouseArea {
131+ anchors.fill: parent
132+ onClicked: root.accepted(passwordInput.text)
133+ }
134+ }
135+ }
136+ ]
137+
138+ onDisplayTextChanged: {
139+ // We use onDisplayTextChanged instead of onTextChanged because
140+ // displayText changes after text and if we did this before it
141+ // updated, we would use the wrong displayText for fakeLabel.
142+ root.loginError = false;
143+ }
144+
145+ onAccepted: respond()
146+
147+ function respond() {
148+ if (root.interactive) {
149+ root.accepted(passwordInput.text);
150+ }
151+ }
152+
153+ Keys.onEscapePressed: {
154+ root.canceled();
155+ event.accepted = true;
156+ }
157+ }
158+
159+ // We use our own custom placeholder label instead of the standard
160+ // TextField one because the standard one hardcodes baseText as the
161+ // palette color, whereas we want raisedSecondaryText.
162+ Label {
163+ id: passwordHint
164+ objectName: "promptHint"
165+ anchors {
166+ left: passwordInput ? passwordInput.left : undefined
167+ right: passwordInput ? passwordInput.right : undefined
168+ verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
169+ leftMargin: units.gu(2)
170+ rightMargin: anchors.leftMargin + extraIcons.width
171+ }
172+ text: root.text
173+ visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
174+ enabled: visible
175+ color: d.drawColor
176+ elide: Text.ElideRight
177+ }
178+
179+ // Have a fake label that covers the text field after the user presses
180+ // enter. What we *really* want is a disabled mode that doesn't lose OSK
181+ // focus. Because our goal here is simply to keep the OSK up while
182+ // we wait for PAM to get back to us, and while waiting, we don't want
183+ // the user to be able to edit the field (simply because it would look
184+ // weird if we allowed that). But until we have such a disabled mode,
185+ // we'll fake it by covering the real text field with a label.
186+ FadingLabel {
187+ id: fakeLabel
188+ anchors.verticalCenter: parent ? parent.verticalCenter : undefined
189+ anchors.left: parent ? parent.left : undefined
190+ anchors.right: parent ? parent.right : undefined
191+ anchors.leftMargin: passwordInput.frameSpacing * 2
192+ anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
193+ color: d.drawColor
194+ text: passwordInput.displayText
195+ visible: !root.interactive
196+ enabled: visible
197+ }
198+}
qml/Greeter/WideView.qml+75−4
@@ -35,6 +35,7 @@ FocusScope {
35 property alias currentIndex: loginList.currentIndex
35 property alias currentIndex: loginList.currentIndex
36 property int delayMinutes // TODO
36 property int delayMinutes // TODO
37 property alias alphanumeric: loginList.alphanumeric
37 property alias alphanumeric: loginList.alphanumeric
38+ property alias hasKeyboard: loginList.hasKeyboard
38 property alias locked: loginList.locked
39 property alias locked: loginList.locked
39 property alias waiting: loginList.waiting
40 property alias waiting: loginList.waiting
40 property var userModel // Set from outside
41 property var userModel // Set from outside
@@ -44,8 +45,7 @@ FocusScope {
44 readonly property bool required: coverPage.required
45 readonly property bool required: coverPage.required
45 readonly property alias sessionToStart: loginList.currentSession
46 readonly property alias sessionToStart: loginList.currentSession
46
47
47− // so that it can be replaced in tests with a mock object
48+ property rect inputMethodRect
48− property var inputMethod: Qt.inputMethod
49
49
50 signal selected(int index)
50 signal selected(int index)
51 signal responded(string response)
51 signal responded(string response)
@@ -97,6 +97,18 @@ FocusScope {
97 width: parent.width
97 width: parent.width
98 draggable: !root.locked && !root.waiting
98 draggable: !root.locked && !root.waiting
99 state: "LoginList"
99 state: "LoginList"
100+ blurAreaHeight: loginList.highlightedHeight + units.gu(4.5)
101+ blurAreaWidth: loginList.width + units.gu(3)
102+ blurAreaX: loginList.x - units.gu(1.5)
103+ blurAreaY: loginList.boxVerticalOffset + loginList.y - units.gu(3)
104+
105+ // Darken background to match CoverPage
106+ Rectangle {
107+ objectName: "lockscreenShade"
108+ anchors.fill: parent
109+ color: "black"
110+ opacity: root.hasCustomBackground ? 0.1 : 0
111+ }
100
112
101 infographics {
113 infographics {
102 anchors.topMargin: parent.height * 0.125
114 anchors.topMargin: parent.height * 0.125
@@ -125,8 +137,7 @@ FocusScope {
125 }
137 }
126
138
127 boxVerticalOffset: (height - highlightedHeight -
139 boxVerticalOffset: (height - highlightedHeight -
128− (inputMethod && inputMethod.visible ?
140+ inputMethodRect.height) / 2
129− inputMethod.keyboardRectangle.height : 0)) / 2
130 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
141 Behavior on boxVerticalOffset { UbuntuNumberAnimation {} }
131
142
132 model: root.userModel
143 model: root.userModel
@@ -175,6 +186,66 @@ FocusScope {
175 }
186 }
176 }
187 }
177
188
189+ // Use an AbstractButton due to icon limitations with Button
190+ AbstractButton {
191+ id: sessionChooser
192+ objectName: "sessionChooserButton"
193+
194+ readonly property url icon: LightDMService.sessions.iconUrl(loginList.currentSession)
195+
196+ visible: LightDMService.sessions.count > 1 &&
197+ !LightDMService.users.data(loginList.currentUserIndex, LightDMService.userRoles.LoggedInRole)
198+
199+ height: units.gu(3.5)
200+ width: units.gu(3.5)
201+
202+ activeFocusOnTab: true
203+ anchors {
204+ right: parent.right
205+ rightMargin: units.gu(2)
206+
207+ bottom: parent.bottom
208+ bottomMargin: units.gu(1.5)
209+ }
210+
211+ Rectangle {
212+ id: badgeHighlight
213+
214+ anchors.fill: parent
215+ visible: parent.activeFocus
216+ color: "transparent"
217+ border.color: theme.palette.normal.focus
218+ border.width: units.dp(1)
219+ radius: 3
220+ }
221+
222+ Icon {
223+ id: badge
224+ anchors.fill: parent
225+ anchors.margins: units.dp(3)
226+ keyColor: "#ffffff" // icon providers give us white icons
227+ color: theme.palette.normal.raisedSecondaryText
228+ source: sessionChooser.icon
229+ }
230+
231+ Keys.onReturnPressed: {
232+ parent.state = "SessionsList";
233+ }
234+
235+ onClicked: {
236+ parent.state = "SessionsList";
237+ }
238+
239+ // Refresh the icon path if looking at different places at runtime
240+ // this is mainly for testing
241+ Connections {
242+ target: LightDMService.sessions
243+ onIconSearchDirectoriesChanged: {
244+ badge.source = LightDMService.sessions.iconUrl(root.currentSession)
245+ }
246+ }
247+ }
248+
178 states: [
249 states: [
179 State {
250 State {
180 name: "SessionsList"
251 name: "SessionsList"
qml/Shell.qml+3−1
@@ -1,4 +1,4 @@
1−/*
1+/*
2 * Copyright (C) 2013-2016 Canonical, Ltd.
2 * Copyright (C) 2013-2016 Canonical, Ltd.
3 * Copyright (C) 2019-2021 UBports Foundation
3 * Copyright (C) 2019-2021 UBports Foundation
4 *
4 *
@@ -440,6 +440,8 @@ StyledItem {
440 background: wallpaperResolver.background
440 background: wallpaperResolver.background
441 backgroundSourceSize: shell.largestScreenDimension
441 backgroundSourceSize: shell.largestScreenDimension
442 hasCustomBackground: wallpaperResolver.hasCustomBackground
442 hasCustomBackground: wallpaperResolver.hasCustomBackground
443+ inputMethodRect: inputMethod.visibleRect
444+ hasKeyboard: shell.hasKeyboard
443 allowFingerprint: !dialogs.hasActiveDialog &&
445 allowFingerprint: !dialogs.hasActiveDialog &&
444 !notifications.topmostIsFullscreen &&
446 !notifications.topmostIsFullscreen &&
445 !panel.indicators.shown
447 !panel.indicators.shown
tests/qmltests/Greeter/tst_WideView.qml+37−17
@@ -55,7 +55,8 @@ StyledItem {
55 delayMinutes: parseInt(delayMinutesField.text, 10)
55 delayMinutes: parseInt(delayMinutesField.text, 10)
56 panelHeight: parseFloat(panelHeightField.text)
56 panelHeight: parseFloat(panelHeightField.text)
57 locked: lockedCheckBox.checked
57 locked: lockedCheckBox.checked
58− inputMethod: fakeInputMethod
58+ inputMethodRect: fakeKeyboard.childrenRect
59+ property var testKeyboard: fakeKeyboard
59
60
60 Component.onDestruction: {
61 Component.onDestruction: {
61 loader.itemDestroyed = true
62 loader.itemDestroyed = true
@@ -66,24 +67,13 @@ StyledItem {
66 currentIndexField.text = index;
67 currentIndexField.text = index;
67 }
68 }
68
69
69− QtObject {
70− id: fakeInputMethod
71− property bool visible: fakeKeyboard.visible
72− property var keyboardRectangle: QtObject {
73− property real x: fakeKeyboard.x
74− property real y: fakeKeyboard.y
75− property real width: fakeKeyboard.width
76− property real height: fakeKeyboard.height
77− }
78− }
79−
80 Rectangle {
70 Rectangle {
81 id: fakeKeyboard
71 id: fakeKeyboard
82 color: "green"
72 color: "green"
83 opacity: 0.7
73 opacity: 0.7
84 anchors.bottom: view.bottom
74 anchors.bottom: view.bottom
85 width: view.width
75 width: view.width
86− height: view.height * 0.6
76+ height: visible ? view.height * 0.6 : 0
87 visible: keyboardVisibleCheckBox.checked
77 visible: keyboardVisibleCheckBox.checked
88 Text {
78 Text {
89 text: "Keyboard Rectangle"
79 text: "Keyboard Rectangle"
@@ -672,13 +662,13 @@ StyledItem {
672 keyboardVisibleCheckBox.checked = true;
662 keyboardVisibleCheckBox.checked = true;
673
663
674 var halfway = (view.height - loginList.highlightedHeight) / 2;
664 var halfway = (view.height - loginList.highlightedHeight) / 2;
675− var halfwayWithOsk = halfway - view.inputMethod.keyboardRectangle.height / 2;
665+ var halfwayWithOsk = halfway - view.inputMethodRect.height / 2;
676 tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
666 tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
677
667
678 var highlightItem = findChild(loginList, "highlightItem");
668 var highlightItem = findChild(loginList, "highlightItem");
679 tryCompareFunction( function() {
669 tryCompareFunction( function() {
680 var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
670 var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
681− return highlightRect.y + highlightRect.height <= view.inputMethod.keyboardRectangle.y;
671+ return highlightRect.y + highlightRect.height <= view.testKeyboard.y;
682 }, true);
672 }, true);
683
673
684 // once the vkb goes away, loginList goes back to its full height
674 // once the vkb goes away, loginList goes back to its full height
@@ -701,9 +691,9 @@ StyledItem {
701
691
702 function test_passcode() {
692 function test_passcode() {
703 var index = selectUser("has-pin");
693 var index = selectUser("has-pin");
704− var promptField = findChild(view, "promptField");
705−
706 view.alphanumeric = false;
694 view.alphanumeric = false;
695+
696+ var promptField = findChild(view, "promptField");
707 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
697 compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
708
698
709 keyClick(Qt.Key_D);
699 keyClick(Qt.Key_D);
@@ -777,5 +767,35 @@ StyledItem {
777 var greeterPrompt = findChild(view, "greeterPrompt0");
767 var greeterPrompt = findChild(view, "greeterPrompt0");
778 verify(greeterPrompt.activeFocus);
768 verify(greeterPrompt.activeFocus);
779 }
769 }
770+
771+ function test_scrollChangesIndex() {
772+ /* Check if the index of the current user gets changed
773+ * if we scroll through the list.
774+ * Expected result: the list has moved at least by one
775+ * user and the index of the user is reported using the
776+ * selected signal in LoginList.qml
777+ * https://github.com/ubports/unity8/issues/397
778+ */
779+ var loginList = findChild(view, "loginList");
780+ // FIXME: Fix scrolling sensitivity (scrolling by -1 shouldn't move more than one user down)
781+ mouseWheel( loginList, loginList.width / 2, loginList.height / 2, 0, -1, null );
782+ selectedSpy.wait();
783+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
784+ }
785+
786+ function test_dragChangesIndex() {
787+ /* Check if the index of the current user gets changed
788+ * if we drag the list or swipe.on it.
789+ * Expected result: the list has moved at least by one
790+ * user and the index of the user is reported using the
791+ * selected signal in LoginList.qml
792+ * https://github.com/ubports/unity8/issues/397
793+ */
794+ var loginList = findChild(view, "loginList");
795+ // FIXME: Fix scrolling sensitivity (the number of scrolled users is randomly changing)
796+ touchFlick(loginList, loginList.width/2, loginList.height/3, loginList.width/2, loginList.height/3 -units.gu(2.1));
797+ selectedSpy.wait();
798+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
799+ }
780 }
800 }
781}
801}
tests/qmltests/tst_OrientedShell.qml+44−3
@@ -40,11 +40,12 @@ Rectangle {
40 height: units.gu(100)
40 height: units.gu(100)
41
41
42 property var tryShell: null
42 property var tryShell: null
43+ property string ldmUserMode: "single"
43
44
44 Binding {
45 Binding {
45 target: LightDMController
46 target: LightDMController
46 property: "userMode"
47 property: "userMode"
47− value: "single"
48+ value: ldmUserMode
48 }
49 }
49
50
50 QtObject {
51 QtObject {
@@ -240,6 +241,23 @@ Rectangle {
240 LightDM.Greeter.showGreeter();
241 LightDM.Greeter.showGreeter();
241 }
242 }
242 }
243 }
244+
245+
246+ Label {
247+ text: "LightDM mock mode"
248+ }
249+
250+ ListItem.ItemSelector {
251+ anchors { left: parent.left; right: parent.right }
252+ activeFocusOnPress: false
253+ model: ["single", "single-passphrase", "single-pin", "full"]
254+ onSelectedIndexChanged: {
255+ testCase.tearDown();
256+ ldmUserMode = model[selectedIndex];
257+ testCase.init();
258+ }
259+ }
260+
243 Label {
261 Label {
244 text: "Physical Orientation:"
262 text: "Physical Orientation:"
245 }
263 }
@@ -1482,7 +1500,8 @@ Rectangle {
1482 touchFlick(shell, touchX, touchY, orientedShell.width * 0.1, touchY);
1500 touchFlick(shell, touchX, touchY, orientedShell.width * 0.1, touchY);
1483
1501
1484 // wait until the animation has finished
1502 // wait until the animation has finished
1503+ if (LightDMController.userMode == "single")
1485− tryCompare(greeter, "shown", false);
1504+ tryCompare(greeter, "shown", false);
1486 waitForRendering(greeter);
1505 waitForRendering(greeter);
1487 }
1506 }
1488
1507
@@ -1493,9 +1512,11 @@ Rectangle {
1493 tryCompare(greeter, "fullyShown", true);
1512 tryCompare(greeter, "fullyShown", true);
1494 }
1513 }
1495
1514
1496− function loadShell(deviceName) {
1515+ function loadShell(deviceName, userMode = "single") {
1497 applicationArguments.deviceName = deviceName;
1516 applicationArguments.deviceName = deviceName;
1498
1517
1518+ ldmUserMode = userMode; // Set the mode for LightDM ( default is "single" )
1519+
1499 // reload our test subject to get it in a fresh state once again
1520 // reload our test subject to get it in a fresh state once again
1500 var orientedShell = createTemporaryObject(shellComponent, shellRect);
1521 var orientedShell = createTemporaryObject(shellComponent, shellRect);
1501
1522
@@ -1697,5 +1718,25 @@ Rectangle {
1697 MockInputDeviceBackend.removeDevice("/touchscreen");
1718 MockInputDeviceBackend.removeDevice("/touchscreen");
1698 tryCompare(tutorial, "paused", true);
1719 tryCompare(tutorial, "paused", true);
1699 }
1720 }
1721+
1722+ /* Check if the keyboard icon on the greeter screen
1723+ * is only shown when an external keyboard is attached
1724+ * and if it is hidden when no keyboard is attached.
1725+ */
1726+ function test_greeterKeyboardDetection() {
1727+ var orientedShell = loadShell("mako", "single-passphrase");
1728+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
1729+
1730+ var greeterPrompt = findChild(orientedShell, "greeterPrompt0");
1731+ verify(greeterPrompt);
1732+
1733+ var promptKeyboard = findChild(greeterPrompt, "greeterPromptKeyboardButton");
1734+
1735+ tryCompare(promptKeyboard, "visible", false);
1736+
1737+ MockInputDeviceBackend.addMockDevice("/kbd0", InputInfo.Keyboard);
1738+
1739+ tryCompare(promptKeyboard, "visible", true);
1740+ }
1700 }
1741 }
1701}
1742}
tests/qmltests/tst_Shell.qml+10−7
@@ -763,13 +763,16 @@ Rectangle {
763 var greeter = findChild(shell, "greeter")
763 var greeter = findChild(shell, "greeter")
764 tryCompare(greeter, "fullyShown", true);
764 tryCompare(greeter, "fullyShown", true);
765
765
766+ if (isButton) {
766− var promptButton = findChild(greeter, "promptButton");
767+ var promptButton = findChild(greeter, "promptButton");
767− tryCompare(promptButton, "visible", isButton);
768+ verify(promptButton);
768−
769− var promptField = findChild(greeter, "promptField");
770− tryCompare(promptField, "visible", !isButton);
769+ tryCompare(promptButton, "visible", true);
771−
772− mouseClick(promptButton);
770+ mouseClick(promptButton);
771+ } else {
772+ var promptField = findChild(greeter, "promptField");
773+ verify(promptField);
774+ tryCompare(promptField, "visible", true);
775+ }
773 }
776 }
774
777
775 function confirmLoggedIn(loggedIn) {
778 function confirmLoggedIn(loggedIn) {