aboutsummaryrefslogtreecommitdiffstats
path: root/theme/components/LoginCard.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-17 14:28:41 +0200
committerDanilo M. <danix@danix.xyz>2026-09-17 14:28:41 +0200
commit8ead58fb5baa8d781159cf443c0f7ffd68cb6775 (patch)
tree83e64a459dd45e028b7073f1d5cc2dafcfd05252 /theme/components/LoginCard.qml
parentcdcf62416feffcb81cb07fdfe2e2dc0320490829 (diff)
downloadsddm-theme-udt-8ead58fb5baa8d781159cf443c0f7ffd68cb6775.tar.gz
sddm-theme-udt-8ead58fb5baa8d781159cf443c0f7ffd68cb6775.zip
feat: replace the login card with a left panel
Draw the greeter as a full-height left panel with a clock, greeting, avatar, a staged username-then-password slot, a power row and the session selector at the bottom. Delete the centered login card.
Diffstat (limited to 'theme/components/LoginCard.qml')
-rw-r--r--theme/components/LoginCard.qml285
1 files changed, 0 insertions, 285 deletions
diff --git a/theme/components/LoginCard.qml b/theme/components/LoginCard.qml
deleted file mode 100644
index e743142..0000000
--- a/theme/components/LoginCard.qml
+++ /dev/null
@@ -1,285 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
-//
-// The login card: avatar, username, password, session choice, feedback and the
-// power bar. Reads the palette straight from `config`; SDDM merges theme.conf
-// and the live theme.conf.user into it.
-
-import QtQuick
-import QtQuick.Controls.Basic
-import QtQuick.Effects
-
-Item {
- id: card
-
- // The wallpaper image to frost behind the card. Null falls back to a flat
- // translucent surface.
- property Item backdropSource: null
- readonly property int radius: 10
- signal wake()
-
- // Shake on a failed login. Main reads this as the card's
- // anchors.horizontalCenterOffset, so the shake never touches the card's
- // own position bindings.
- property real shakeOffset: 0
-
- implicitWidth: 380
- implicitHeight: column.implicitHeight + 2 * 28
-
- // Frosted surface: the wallpaper behind the card, blurred and tinted.
- Backdrop {
- anchors.fill: parent
- radius: card.radius
- sourceItem: card.backdropSource
- tint: config.stringValue("mantle")
- blurEnabled: config.boolValue("blur")
- }
-
- Rectangle {
- anchors.fill: parent
- radius: card.radius
- color: "transparent"
- border.width: 2
- border.color: config.stringValue("accent")
- }
-
- // Avatar lookup. userModel is a list model; Repeater delegates expose its
- // roles by name. The matching face is copied out on demand because the
- // username can change.
- property string avatarPath: ""
- readonly property string initials:
- usernameField.text.length > 0 ? usernameField.text.charAt(0).toUpperCase() : "?"
-
- Repeater {
- id: faces
- model: userModel
- delegate: Item {
- readonly property string faceName: name
- readonly property string faceIcon: icon
- visible: false
- }
- }
-
- function refreshAvatar() {
- for (var i = 0; i < faces.count; ++i) {
- var it = faces.itemAt(i)
- if (it && it.faceName === usernameField.text) {
- avatarPath = it.faceIcon || ""
- return
- }
- }
- avatarPath = ""
- }
-
- Column {
- id: column
- anchors.centerIn: parent
- width: parent.width - 2 * 28
- spacing: 16
-
- Item {
- width: 96
- height: 96
- anchors.horizontalCenter: parent.horizontalCenter
-
- Rectangle {
- anchors.fill: parent
- radius: width / 2
- color: config.stringValue("surface0")
- visible: avatarImage.status !== Image.Ready
- Text {
- anchors.centerIn: parent
- text: card.initials
- color: config.stringValue("text")
- font.family: "Noto Sans"
- font.pixelSize: 34
- }
- }
-
- Image {
- id: avatarImage
- anchors.fill: parent
- source: card.avatarPath
- fillMode: Image.PreserveAspectCrop
- visible: false
- }
-
- MultiEffect {
- anchors.fill: parent
- source: avatarImage
- visible: avatarImage.status === Image.Ready
- maskEnabled: true
- maskSource: avatarMask
- }
-
- Rectangle {
- id: avatarMask
- anchors.fill: parent
- radius: width / 2
- layer.enabled: true
- visible: false
- }
-
- Rectangle {
- anchors.fill: parent
- radius: width / 2
- color: "transparent"
- border.width: 2
- border.color: config.stringValue("accent")
- }
- }
-
- TextField {
- id: usernameField
- width: parent.width
- text: userModel.lastUser !== undefined ? userModel.lastUser : ""
- placeholderText: "Username"
- font.family: "Noto Sans"
- color: config.stringValue("text")
- palette.base: config.stringValue("surface0")
- palette.text: config.stringValue("text")
- palette.highlight: config.stringValue("accent")
- palette.highlightedText: config.stringValue("base")
- background: Rectangle {
- radius: 6
- color: config.stringValue("surface0")
- }
- onTextChanged: card.refreshAvatar()
- onAccepted: card.login()
- Keys.onPressed: event => {
- card.wake()
- event.accepted = false
- }
- }
-
- TextField {
- id: passwordField
- width: parent.width
- echoMode: TextInput.Password
- placeholderText: "Password"
- font.family: "Noto Sans"
- color: config.stringValue("text")
- palette.base: config.stringValue("surface0")
- palette.text: config.stringValue("text")
- palette.highlight: config.stringValue("accent")
- palette.highlightedText: config.stringValue("base")
- background: Rectangle {
- radius: 6
- color: config.stringValue("surface0")
- }
- onAccepted: card.login()
- Keys.onPressed: event => {
- card.wake()
- event.accepted = false
- }
- }
-
- Row {
- width: parent.width
- spacing: 8
-
- ComboBox {
- id: sessionBox
- width: parent.width - loginButton.width - parent.spacing
- model: sessionModel
- textRole: "name"
- currentIndex: sessionModel.lastIndex
- font.family: "Noto Sans"
- palette.base: config.stringValue("surface0")
- palette.text: config.stringValue("text")
- palette.highlight: config.stringValue("accent")
- palette.highlightedText: config.stringValue("base")
- background: Rectangle {
- radius: 6
- color: config.stringValue("surface0")
- }
- }
-
- Button {
- id: loginButton
- text: "Log in"
- font.family: "Noto Sans"
- palette.button: config.stringValue("accent")
- palette.buttonText: config.stringValue("base")
- background: Rectangle {
- radius: 6
- color: config.stringValue("accent")
- }
- contentItem: Text {
- text: loginButton.text
- color: config.stringValue("base")
- font: loginButton.font
- horizontalAlignment: Text.AlignHCenter
- verticalAlignment: Text.AlignVCenter
- padding: 8
- }
- onClicked: card.login()
- }
- }
-
- PowerBar {
- anchors.horizontalCenter: parent.horizontalCenter
- }
-
- Text {
- width: parent.width
- horizontalAlignment: Text.AlignHCenter
- wrapMode: Text.WordWrap
- font.family: "Noto Sans"
- font.pixelSize: 12
- color: card.loginError !== ""
- ? config.stringValue("red")
- : config.stringValue("yellow")
- text: card.message
- }
- }
-
- function login() {
- if (usernameField.text.length === 0)
- return
- sddm.login(usernameField.text, passwordField.text, sessionBox.currentIndex)
- }
-
- // Return keyboard focus after the idle fade disabled the card: password
- // when a username is already filled, else the username field.
- function restoreFocus() {
- if (usernameField.text.length > 0)
- passwordField.forceActiveFocus()
- else
- usernameField.forceActiveFocus()
- }
-
- // Login failure wins the notice line; otherwise Caps Lock, if on.
- property string loginError: ""
- readonly property string message: {
- if (loginError !== "")
- return loginError
- if (keyboard.capsLock)
- return "Caps Lock is on"
- return ""
- }
-
- Connections {
- target: sddm
- function onLoginFailed() {
- loginError = "Login failed"
- passwordField.text = ""
- shake.restart()
- }
- function onLoginSucceeded() {
- loginError = ""
- }
- }
-
- SequentialAnimation {
- id: shake
- NumberAnimation { target: card; property: "shakeOffset"; to: -10; duration: 40 }
- NumberAnimation { target: card; property: "shakeOffset"; to: 10; duration: 40 }
- NumberAnimation { target: card; property: "shakeOffset"; to: 0; duration: 40 }
- }
-
- Component.onCompleted: {
- refreshAvatar()
- usernameField.forceActiveFocus()
- }
-}