diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-17 14:28:41 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-17 14:28:41 +0200 |
| commit | 8ead58fb5baa8d781159cf443c0f7ffd68cb6775 (patch) | |
| tree | 83e64a459dd45e028b7073f1d5cc2dafcfd05252 /theme/components/LoginCard.qml | |
| parent | cdcf62416feffcb81cb07fdfe2e2dc0320490829 (diff) | |
| download | sddm-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.qml | 285 |
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() - } -} |
