Unity 8
LoginList.qml
1 /*
2  * Copyright (C) 2013-2016 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.12
18 import QtGraphicalEffects 1.0
19 import Ubuntu.Components 1.3
20 import "../Components"
21 import "." 0.1
22 
23 StyledItem {
24  id: root
25  focus: true
26 
27  property alias model: userList.model
28  property alias alphanumeric: promptList.alphanumeric
29  property alias hasKeyboard: promptList.hasKeyboard
30  property int currentIndex
31  property bool locked
32  property bool waiting
33  property alias boxVerticalOffset: highlightItem.y
34  property string _realName
35 
36  readonly property int numAboveBelow: 4
37  readonly property int cellHeight: units.gu(5)
38  readonly property int highlightedHeight: highlightItem.height
39  readonly property int moveDuration: UbuntuAnimation.FastDuration
40  property string currentSession // Initially set by LightDM
41  readonly property string currentUser: userList.currentItem.username
42 
43  readonly property alias currentUserIndex: userList.currentIndex
44 
45  signal responded(string response)
46  signal selected(int index)
47  signal sessionChooserButtonClicked()
48 
49  function tryToUnlock() {
50  promptList.forceActiveFocus();
51  }
52 
53  function showError() {
54  promptList.loginError = true;
55  wrongPasswordAnimation.start();
56  }
57 
58  function showFakePassword() {
59  promptList.interactive = false;
60  promptList.showFakePassword();
61  }
62 
63  theme: ThemeSettings {
64  name: "Ubuntu.Components.Themes.Ambiance"
65  }
66 
67  Keys.onUpPressed: {
68  if (currentIndex > 0) {
69  selected(currentIndex - 1);
70  }
71  event.accepted = true;
72  }
73  Keys.onDownPressed: {
74  if (currentIndex + 1 < model.count) {
75  selected(currentIndex + 1);
76  }
77  event.accepted = true;
78  }
79  Keys.onEscapePressed: {
80  selected(currentIndex);
81  event.accepted = true;
82  }
83 
84  onCurrentIndexChanged: {
85  userList.currentIndex = currentIndex;
86  promptList.loginError = false;
87  }
88 
89  LoginAreaContainer {
90  id: highlightItem
91  objectName: "highlightItem"
92  anchors {
93  left: parent.left
94  leftMargin: units.gu(2)
95  right: parent.right
96  rightMargin: units.gu(2)
97  }
98 
99  height: Math.max(units.gu(15), promptList.height + units.gu(8))
100  Behavior on height { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
101  }
102 
103  ListView {
104  id: userList
105  objectName: "userList"
106 
107  anchors.fill: parent
108  anchors.leftMargin: units.gu(2)
109  anchors.rightMargin: units.gu(2)
110 
111  preferredHighlightBegin: highlightItem.y + units.gu(1.5)
112  preferredHighlightEnd: highlightItem.y + units.gu(1.5)
113  highlightRangeMode: ListView.StrictlyEnforceRange
114  highlightMoveDuration: root.moveDuration
115  interactive: count > 1
116 
117  readonly property bool movingInternally: moveTimer.running || userList.moving
118 
119  onMovingChanged: if (!moving) root.selected(currentIndex)
120 
121  onCurrentIndexChanged: {
122  moveTimer.start();
123  }
124 
125  onCountChanged: if (root.currentIndex >= count) root.selected(0)
126 
127  delegate: Item {
128  width: userList.width
129  height: root.cellHeight
130 
131  readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex)
132  readonly property bool aboveCurrent: (userList.currentIndex > 0 && index < 0) || (userList.currentIndex >= 0 && index < userList.currentIndex)
133  readonly property int belowOffset: root.highlightedHeight - root.cellHeight
134  readonly property string userSession: session
135  readonly property string username: name ? name : ""
136 
137  opacity: {
138  // The goal here is to make names less and less opaque as they
139  // leave the highlight area. Can't simply use index, because
140  // that can change quickly if the user clicks at edges of
141  // list. So we use actual pixel distance.
142  var highlightDist = 0;
143  var realY = y - userList.contentY;
144  if (belowHighlight)
145  realY += belowOffset;
146  if (realY + height <= highlightItem.y)
147  highlightDist = realY + height - highlightItem.y;
148  else if (realY >= highlightItem.y + root.highlightedHeight)
149  highlightDist = realY - highlightItem.y - root.highlightedHeight;
150  else
151  return 1;
152  return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight))
153  }
154 
155  Row {
156  spacing: units.gu(1)
157 // 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
158 
159  anchors {
160  leftMargin: units.gu(2)
161  rightMargin: units.gu(2)
162  horizontalCenter: parent.horizontalCenter
163  bottom: parent.top
164  // Add an offset to bottomMargin for any items below the highlight
165  bottomMargin: -(units.gu(4) + (parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0))
166  }
167 
168  Rectangle {
169  id: activeIndicator
170  anchors.verticalCenter: parent.verticalCenter
171  color: theme.palette.normal.raised
172  visible: userList.count > 1 && loggedIn
173  height: visible ? units.gu(0.5) : 0
174  width: height
175  }
176 
177  Icon {
178  id: userIcon
179  name: "account"
180  height: userList.currentIndex === index ? units.gu(4.5) : units.gu(3)
181  width: height
182  color: theme.palette.normal.raisedSecondaryText
183  anchors.verticalCenter: parent.verticalCenter
184  }
185 
186  Column {
187  anchors.verticalCenter: parent.verticalCenter
188  spacing: units.gu(0.25)
189 
190  FadingLabel {
191  objectName: "username" + index
192 
193  text: userList.currentIndex === index
194  && name === "*other"
195  && LightDMService.greeter.authenticationUser !== ""
196  ? LightDMService.greeter.authenticationUser : realName ? realName : ""
197  color: userList.currentIndex !== index ? theme.palette.normal.raised
198  : theme.palette.normal.raisedSecondaryText
199  font.weight: userList.currentIndex === index ? Font.Normal : Font.Light
200  font.pointSize: units.gu(2)
201 
202  width: highlightItem.width
203  && contentWidth > highlightItem.width - userIcon.width - units.gu(4)
204  ? highlightItem.width - userIcon.width - units.gu(4)
205  : contentWidth
206 
207  Component.onCompleted: _realName = realName
208 
209  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
210  }
211 
212  Row {
213  spacing: units.gu(1)
214 
215  FadingLabel {
216  text: root.alphanumeric ? "Login with password" : "Login with pin"
217  color: theme.palette.normal.raisedSecondaryText
218  visible: userList.currentIndex === index && false
219  font.weight: Font.Light
220  font.pointSize: units.gu(1.25)
221  }
222  }
223  }
224  }
225 
226  MouseArea {
227  anchors {
228  left: parent.left
229  right: parent.right
230  top: parent.top
231  // Add an offset to topMargin for any items below the highlight
232  topMargin: parent.belowHighlight ? parent.belowOffset : parent.aboveCurrent ? -units.gu(5) : 0
233  }
234  height: parent.height
235  enabled: userList.currentIndex !== index && parent.opacity > 0
236  onClicked: root.selected(index)
237 
238  Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } }
239  }
240  }
241 
242  // This is needed because ListView.moving is not true if the ListView
243  // moves because of an internal event (e.g. currentIndex has changed)
244  Timer {
245  id: moveTimer
246  running: false
247  repeat: false
248  interval: root.moveDuration
249  }
250  }
251 
252  PromptList {
253  id: promptList
254  objectName: "promptList"
255  anchors {
256  bottom: highlightItem.bottom
257  horizontalCenter: highlightItem.horizontalCenter
258  margins: units.gu(2)
259  }
260  width: highlightItem.width - anchors.margins * 2
261 
262  focus: true
263 
264  onClicked: {
265  interactive = false;
266  if (root.locked) {
267  root.selected(currentIndex);
268  } else {
269  root.responded("");
270  }
271  }
272  onResponded: {
273  interactive = false;
274  root.responded(text);
275  }
276  onCanceled: {
277  interactive = false;
278  root.selected(currentIndex);
279  }
280 
281  Connections {
282  target: LightDMService.prompts
283  onModelReset: promptList.interactive = true
284  }
285  }
286 
287  WrongPasswordAnimation {
288  id: wrongPasswordAnimation
289  objectName: "wrongPasswordAnimation"
290  target: promptList
291  }
292 }