diff --git a/quickshell/Modules/Greetd/GreeterContent.qml b/quickshell/Modules/Greetd/GreeterContent.qml index 5423a57e..d7a22547 100644 --- a/quickshell/Modules/Greetd/GreeterContent.qml +++ b/quickshell/Modules/Greetd/GreeterContent.qml @@ -63,8 +63,11 @@ Item { readonly property bool greeterExternalAuthAvailable: (greeterPamHasFprint && GreetdSettings.greeterEnableFprint) || (greeterPamHasU2f && GreetdSettings.greeterEnableU2f) readonly property bool greeterPamHasExternalAuth: greeterPamHasFprint || greeterPamHasU2f readonly property bool multipleUsersAvailable: GreeterUsersService.loaded && GreeterUsersService.users.length > 1 - readonly property bool showUserPicker: multipleUsersAvailable && !GreeterState.showPasswordInput + readonly property bool showUserPicker: multipleUsersAvailable && !GreeterState.showPasswordInput && !manualUsernameEntry + readonly property bool showAccountSwitchLink: multipleUsersAvailable && !GreeterState.showPasswordInput && !GreeterState.unlocking + readonly property int userPickerMaxHeight: Math.min(400, Math.max(120, height * 0.35)) property bool userListOpen: false + property bool manualUsernameEntry: false property bool skipAutoSelectUser: false property string pickerThemeUsername: "" @@ -454,9 +457,34 @@ Item { } } + function enterManualUsernameEntry() { + if (!root.multipleUsersAvailable || GreeterState.showPasswordInput) + return; + root.manualUsernameEntry = true; + root.userListOpen = false; + GreeterState.username = ""; + GreeterState.usernameInput = ""; + GreeterState.selectedUserIndex = -1; + inputField.text = ""; + root.applyPickerPreviewTheme(); + Qt.callLater(() => inputField.forceActiveFocus()); + } + + function returnToUserListFromManualEntry() { + if (!root.multipleUsersAvailable) + return; + root.manualUsernameEntry = false; + root.userListOpen = true; + GreeterState.username = ""; + GreeterState.usernameInput = ""; + inputField.text = ""; + root.applyPickerPreviewTheme(); + } + function returnToUserPicker() { if (!root.multipleUsersAvailable || GreeterState.unlocking) return; + root.manualUsernameEntry = false; root.skipAutoSelectUser = true; awaitingExternalAuth = false; pendingPasswordResponse = false; @@ -483,6 +511,7 @@ Item { const user = (rawValue || "").trim(); if (!user) return; + root.manualUsernameEntry = false; root.skipAutoSelectUser = false; submitUsername(user, skipDropdownUpdate === true); } @@ -1025,6 +1054,8 @@ Item { onClicked: { if (GreeterState.showPasswordInput) root.returnToUserPicker(); + else if (root.manualUsernameEntry) + root.returnToUserListFromManualEntry(); else root.userListOpen = !root.userListOpen; } @@ -1050,6 +1081,7 @@ Item { anchors.verticalCenter: root.userListOpen ? undefined : parent.verticalCenter anchors.top: root.userListOpen ? parent.top : undefined anchors.margins: Theme.spacingM + maxExpandedHeight: root.userPickerMaxHeight visible: root.showUserPicker && !GreeterState.showPasswordInput expanded: root.userListOpen onUserSelected: username => root.selectUser(username, false) @@ -1291,6 +1323,36 @@ Item { } } + Item { + Layout.fillWidth: true + Layout.preferredHeight: root.showAccountSwitchLink ? 28 : 0 + visible: root.showAccountSwitchLink + + StyledText { + id: accountSwitchLabel + + anchors.horizontalCenter: parent.horizontalCenter + text: root.manualUsernameEntry ? I18n.tr("Back to user list", "greeter link to return from manual username entry to user picker") : I18n.tr("Not listed?", "greeter link to switch to manual username entry") + color: Theme.primary + font.pixelSize: Theme.fontSizeSmall + font.underline: accountSwitchMouse.containsMouse + } + + MouseArea { + id: accountSwitchMouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (root.manualUsernameEntry) + root.returnToUserListFromManualEntry(); + else + root.enterManualUsernameEntry(); + } + } + } + StyledText { Layout.fillWidth: true Layout.preferredHeight: 38 diff --git a/quickshell/Modules/Greetd/GreeterUserPicker.qml b/quickshell/Modules/Greetd/GreeterUserPicker.qml index 8c890db8..b0d55501 100644 --- a/quickshell/Modules/Greetd/GreeterUserPicker.qml +++ b/quickshell/Modules/Greetd/GreeterUserPicker.qml @@ -8,10 +8,23 @@ Item { id: root property bool expanded: false + property int maxExpandedHeight: 400 signal userSelected(string username) signal toggleRequested() + readonly property int rowHeight: 52 + readonly property int collapsedBarHeight: 36 + readonly property int expandedListHeight: { + if (!expanded) + return 0; + const count = GreeterUsersService.users.length; + if (count === 0) + return 0; + const fullHeight = count * rowHeight + Math.max(0, count - 1) * Theme.spacingXS; + return Math.min(maxExpandedHeight, fullHeight); + } + function encodeFileUrl(path) { if (!path) return ""; @@ -25,116 +38,117 @@ Item { return ""; } - implicitHeight: column.implicitHeight + implicitHeight: expanded ? expandedListHeight : collapsedBarHeight implicitWidth: parent ? parent.width : 320 - ColumnLayout { - id: column + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: expanded ? undefined : parent.verticalCenter + height: collapsedBarHeight + visible: !expanded && !!GreeterState.username + spacing: Theme.spacingM + + StyledText { + Layout.fillWidth: true + text: GreeterUsersService.optionLabel(GreeterState.username) + color: Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + elide: Text.ElideRight + } + + DankIcon { + name: "expand_more" + size: 20 + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.toggleRequested() + } + } + + Item { + anchors.left: parent.left + anchors.right: parent.right + height: collapsedBarHeight + visible: !expanded && !GreeterState.username + + DankIcon { + anchors.centerIn: parent + name: "expand_more" + size: 20 + color: Theme.surfaceVariantText + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: root.toggleRequested() + } + } + + DankListView { + id: userListView anchors.left: parent.left anchors.right: parent.right - spacing: Theme.spacingS + anchors.top: parent.top + height: expandedListHeight + visible: expanded + clip: true + interactive: contentHeight > height + spacing: Theme.spacingXS + model: GreeterUsersService.users - RowLayout { - Layout.fillWidth: true - spacing: Theme.spacingM - visible: !root.expanded && !!GreeterState.username + delegate: Rectangle { + id: userRow - StyledText { - Layout.fillWidth: true - text: GreeterUsersService.optionLabel(GreeterState.username) - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - elide: Text.ElideRight - } + required property var modelData + required property int index - DankIcon { - name: "expand_more" - size: 20 - color: Theme.surfaceVariantText - } + width: userListView.width + height: root.rowHeight + radius: Theme.cornerRadius + color: userRowMouse.containsMouse ? Theme.surfacePressed : "transparent" + border.color: GreeterState.username === userRow.modelData.username ? Theme.primary : "transparent" + border.width: GreeterState.username === userRow.modelData.username ? 1 : 0 - MouseArea { + RowLayout { anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: root.toggleRequested() - } - } + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: Theme.spacingM - Item { - Layout.fillWidth: true - Layout.preferredHeight: 36 - visible: !root.expanded && !GreeterState.username + Item { + Layout.preferredWidth: 36 + Layout.preferredHeight: 36 - DankIcon { - anchors.centerIn: parent - name: "expand_more" - size: 20 - color: Theme.surfaceVariantText - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: root.toggleRequested() - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: Theme.spacingXS - visible: root.expanded - - Repeater { - model: GreeterUsersService.users - - delegate: Rectangle { - id: userRow - - required property var modelData - - Layout.fillWidth: true - Layout.preferredHeight: 52 - radius: Theme.cornerRadius - color: userRowMouse.containsMouse ? Theme.surfacePressed : "transparent" - border.color: GreeterState.username === userRow.modelData.username ? Theme.primary : "transparent" - border.width: GreeterState.username === userRow.modelData.username ? 1 : 0 - - RowLayout { + DankCircularImage { anchors.fill: parent - anchors.leftMargin: Theme.spacingS - anchors.rightMargin: Theme.spacingS - spacing: Theme.spacingM - - Item { - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 - - DankCircularImage { - anchors.fill: parent - imageSource: root.profileImageSource(userRow.modelData.username) - fallbackIcon: "person" - } - } - - StyledText { - Layout.fillWidth: true - text: GreeterUsersService.optionLabel(userRow.modelData.username) - color: Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - elide: Text.ElideRight - } - } - - MouseArea { - id: userRowMouse - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: root.userSelected(userRow.modelData.username) + imageSource: root.profileImageSource(userRow.modelData.username) + fallbackIcon: "person" } } + + StyledText { + Layout.fillWidth: true + text: GreeterUsersService.optionLabel(userRow.modelData.username) + color: Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + elide: Text.ElideRight + } + } + + MouseArea { + id: userRowMouse + + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: root.userSelected(userRow.modelData.username) } } }