Completely make it from scratch

This commit is contained in:
=
2025-08-02 17:41:49 -05:00
parent a53ff9410f
commit 68e091a4a7
17 changed files with 157 additions and 910 deletions

575
Main.qml
View File

@@ -6,450 +6,187 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15 as QQC2
import Qt5Compat.GraphicalEffects
import org.kde.plasma.components 3.0 as PlasmaComponents3
import org.kde.plasma.private.keyboardindicator as KeyboardIndicator
import org.kde.kirigami 2.20 as Kirigami
import org.kde.breeze.components
import QtQuick.Controls 2.15
Item {
id: root
id: mainStack
property bool usingCustomUser: false
property bool attemptingToLogin: false
// If we're using software rendering, draw outlines instead of shadows
// See https://bugs.kde.org/show_bug.cgi?id=398317
readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Kirigami.Theme.inherit: false
width: 1600
height: 900
property string notificationMessage
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
KeyboardIndicator.KeyState {
id: capsLockState
key: Qt.Key_CapsLock
}
Item {
id: wallpaper
// Background Image
Image {
id: sceneImageBackground
source: "background.png"
anchors.fill: parent
Repeater {
model: screenModel
sourceSize.width: parent.width
sourceSize.height: parent.height
fillMode: Image.PreserveAspectCrop
smooth: true;
}
Background {
x: geometry.x; y: geometry.y; width: geometry.width; height: geometry.height
sceneBackgroundType: config.type
sceneBackgroundColor: config.color
sceneBackgroundImage: config.background
// Session
ComboBox {
id: session
model: sessionModel
currentIndex: sessionModel.lastIndex
textRole: "name"
anchors {
top: parent.top
left: parent.left
topMargin: 75
leftMargin: 10
}
height: 50
background: Rectangle {
color: "transparent"
}
font.family: "Rajdhani"
font.capitalization: Font.AllUppercase
font.bold: true
font.pointSize: 30
indicator: Image {
id: sessionArrow
source: "icons/down-arrow.png"
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
width: 36
height: 36
fillMode: Image.PreserveAspectFit
rotation: session.popup.visible ? 180 : 0
Behavior on rotation { NumberAnimation { duration: 150; easing.type: Easing.InOutQuad } }
}
}
// User Switcher
Rectangle {
anchors {
top: parent.top
right: parent.right
topMargin: 68
rightMargin: 3
}
width: 50
height: 50
color: "#FFFFFF"
opacity: 0
radius: 5
MouseArea {
hoverEnabled: true
anchors.fill: parent
onClicked: {
usingCustomUser = !usingCustomUser
}
onEntered: {
parent.opacity = 0.2
}
onExited: {
parent.opacity = 0
}
onPressed: {
parent.opacity = 0.3
}
onReleased: {
parent.opacity = 0.2
}
}
}
RejectPasswordAnimation {
id: rejectPasswordAnimation
target: mainStack
Image {
anchors {
top: parent.top
right: parent.right
topMargin: 75
rightMargin: 10
}
source: "icons/user.png"
width: 36
height: 36
fillMode: Image.PreserveAspectFit
}
MouseArea {
id: loginScreenRoot
anchors.fill: parent
// Text Fields
ColumnLayout {
anchors.horizontalCenter: parent.horizontalCenter
y: 975
property bool uiVisible: true
property bool blockUI: mainStack.depth > 1 || userListComponent.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive || config.type !== "image"
// Username
Item {
Layout.preferredHeight: 100
visible: usingCustomUser
hoverEnabled: true
drag.filterChildren: true
onPressed: uiVisible = true;
onPositionChanged: uiVisible = true;
onUiVisibleChanged: {
if (blockUI) {
fadeoutTimer.running = false;
} else if (uiVisible) {
fadeoutTimer.restart();
Image {
id: usernameBg
source: "text-field"
x: -(sourceSize.width / 2)
}
}
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
uiVisible = true;
} else {
fadeoutTimer.restart();
}
}
Keys.onPressed: event => {
uiVisible = true;
event.accepted = false;
}
//takes one full minute for the ui to disappear
Timer {
id: fadeoutTimer
running: true
interval: 60000
onTriggered: {
if (!loginScreenRoot.blockUI) {
userListComponent.mainPasswordBox.showPassword = false;
loginScreenRoot.uiVisible = false;
TextField {
id: usernameField
enabled: !attemptingToLogin
focus: usingCustomUser
placeholderText: "Username"
x: -(usernameBg.sourceSize.width / 2) + 6
y: 5
width: 955
height: 75
background: Rectangle {
color: "transparent"
}
font.family: "Rajdhani"
font.pointSize: 30
font.bold: true
Keys.onEscapePressed: {
mainStack.forceActiveFocus();
}
onAccepted: {
passwordField.forceActiveFocus();
}
}
}
QQC2.StackView {
id: mainStack
anchors {
left: parent.left
right: parent.right
// Password
Item {
Layout.preferredHeight: 100
Image {
id: passwordBg
source: "text-field"
x: -(sourceSize.width / 2)
}
height: root.height + Kirigami.Units.gridUnit * 3
// If true (depends on the style and environment variables), hover events are always accepted
// and propagation stopped. This means the parent MouseArea won't get them and the UI won't be shown.
// Disable capturing those events while the UI is hidden to avoid that, while still passing events otherwise.
// One issue is that while the UI is visible, mouse activity won't keep resetting the timer, but when it
// finally expires, the next event should immediately set uiVisible = true again.
hoverEnabled: loginScreenRoot.uiVisible ? undefined : false
focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it
Timer {
//SDDM has a bug in 0.13 where even though we set the focus on the right item within the window, the window doesn't have focus
//it is fixed in 6d5b36b28907b16280ff78995fef764bb0c573db which will be 0.14
//we need to call "window->activate()" *After* it's been shown. We can't control that in QML so we use a shoddy timer
//it's been this way for all Plasma 5.x without a huge problem
running: true
repeat: false
interval: 200
onTriggered: mainStack.forceActiveFocus()
}
initialItem: Login {
id: userListComponent
userList.opacity: 0
userListModel: userModel
loginScreenUiVisible: loginScreenRoot.uiVisible
userListCurrentIndex: userModel.lastIndex >= 0 ? userModel.lastIndex : 0
lastUserName: userModel.lastUser
showUserList: {
if (!userListModel.hasOwnProperty("count")
|| !userListModel.hasOwnProperty("disableAvatarsThreshold")) {
return false
}
if (userListModel.count === 0 ) {
return false
}
if (userListModel.hasOwnProperty("containsAllUsers") && !userListModel.containsAllUsers) {
return false
}
return userListModel.count <= userListModel.disableAvatarsThreshold
TextField {
id: passwordField
enabled: !attemptingToLogin
focus: !usingCustomUser
echoMode: TextInput.Password
placeholderText: "Password"
x: -(passwordBg.sourceSize.width / 2) + 6
y: 5
width: 955
height: 75
background: Rectangle {
color: "transparent"
}
notificationMessage: {
const parts = [];
if (capsLockState.locked) {
parts.push(i18nd("plasma-desktop-sddm-theme", "Caps Lock is on"));
}
if (root.notificationMessage) {
parts.push(root.notificationMessage);
}
return parts.join(" • ");
font.family: "Rajdhani"
font.pointSize: 30
font.bold: true
Keys.onEscapePressed: {
mainStack.forceActiveFocus();
}
actionItemsVisible: !inputPanel.keyboardActive
actionItems: [
/*ActionButton {
icon.name: "system-suspend"
text: i18ndc("plasma-desktop-sddm-theme", "Suspend to RAM", "Sleep")
onClicked: sddm.suspend()
enabled: sddm.canSuspend
},
ActionButton {
icon.name: "system-reboot"
text: i18nd("plasma-desktop-sddm-theme", "Restart")
onClicked: sddm.reboot()
enabled: sddm.canReboot
},
ActionButton {
icon.name: "system-shutdown"
text: i18nd("plasma-desktop-sddm-theme", "Shut Down")
onClicked: sddm.powerOff()
enabled: sddm.canPowerOff
},
ActionButton {
icon.name: "system-user-prompt"
text: i18ndc("plasma-desktop-sddm-theme", "For switching to a username and password prompt", "Other…")
onClicked: mainStack.push(userPromptComponent)
visible: !userListComponent.showUsernamePrompt
}*/]
onLoginRequest: {
root.notificationMessage = ""
sddm.login(username, password, sessionButton.currentIndex)
onAccepted: {
attemptingToLogin = true
sddm.login(usernameField.text, passwordField.text, session.index)
event.accepted = true
}
}
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
Connections {
target: sddm
function onLoginFailed() {
passwordField.selectAll()
passwordField.forceActiveFocus()
attemptingToLogin = false
}
}
readonly property real zoomFactor: 1.5
popEnter: Transition {
ScaleAnimator {
from: mainStack.zoomFactor
to: 1
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
OpacityAnimator {
from: 0
to: 1
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
}
popExit: Transition {
ScaleAnimator {
from: 1
to: 1 / mainStack.zoomFactor
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
OpacityAnimator {
from: 1
to: 0
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
}
pushEnter: Transition {
ScaleAnimator {
from: 1 / mainStack.zoomFactor
to: 1
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
OpacityAnimator {
from: 0
to: 1
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
}
pushExit: Transition {
ScaleAnimator {
from: 1
to: mainStack.zoomFactor
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
OpacityAnimator {
from: 1
to: 0
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
}
}
VirtualKeyboardLoader {
id: inputPanel
z: 1
screenRoot: root
mainStack: mainStack
mainBlock: userListComponent
passwordField: userListComponent.mainPasswordBox
}
Component {
id: userPromptComponent
Login {
showUsernamePrompt: true
notificationMessage: root.notificationMessage
loginScreenUiVisible: loginScreenRoot.uiVisible
fontSize: Kirigami.Theme.defaultFont.pointSize + 2
// using a model rather than a QObject list to avoid QTBUG-75900
userListModel: ListModel {
ListElement {
name: ""
icon: ""
}
Component.onCompleted: {
// as we can't bind inside ListElement
setProperty(0, "name", i18nd("plasma-desktop-sddm-theme", "Type in Username and Password"));
setProperty(0, "icon", Qt.resolvedUrl("faces/.face.icon"))
}
}
onLoginRequest: {
root.notificationMessage = ""
sddm.login(username, password, sessionButton.currentIndex)
}
actionItemsVisible: !inputPanel.keyboardActive
actionItems: [
ActionButton {
icon.name: "system-suspend"
text: i18ndc("plasma-desktop-sddm-theme", "Suspend to RAM", "Sleep")
onClicked: sddm.suspend()
enabled: sddm.canSuspend
},
ActionButton {
icon.name: "system-reboot"
text: i18nd("plasma-desktop-sddm-theme", "Restart")
onClicked: sddm.reboot()
enabled: sddm.canReboot
},
ActionButton {
icon.name: "system-shutdown"
text: i18nd("plasma-desktop-sddm-theme", "Shut Down")
onClicked: sddm.powerOff()
enabled: sddm.canPowerOff
},
ActionButton {
icon.name: "system-user-list"
text: i18nd("plasma-desktop-sddm-theme", "List Users")
onClicked: mainStack.pop()
}
]
}
}
// Note: Containment masks stretch clickable area of their buttons to
// the screen edges, essentially making them adhere to Fitts's law.
// Due to virtual keyboard button having an icon, buttons may have
// different heights, so fillHeight is required.
//
// Note for contributors: Keep this in sync with LockScreenUi.qml footer.
RowLayout {
id: footer
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
margins: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
}
}
/*PlasmaComponents3.ToolButton {
id: virtualKeyboardButton
text: i18ndc("plasma-desktop-sddm-theme", "Button to show/hide virtual keyboard")
icon.name: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"
onClicked: {
// Otherwise the password field loses focus and virtual keyboard
// keystrokes get eaten
userListComponent.mainPasswordBox.forceActiveFocus();
inputPanel.showHide()
}
visible: inputPanel.status === Loader.Ready
Layout.fillHeight: true
containmentMask: Item {
parent: virtualKeyboardButton
anchors.fill: parent
anchors.leftMargin: -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}*/
KeyboardButton {
id: keyboardButton
onKeyboardLayoutChanged: {
// Otherwise the password field loses focus and virtual keyboard
// keystrokes get eaten
userListComponent.mainPasswordBox.forceActiveFocus();
}
Layout.fillHeight: true
containmentMask: Item {
parent: keyboardButton
anchors.fill: parent
anchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
SessionButton {
id: sessionButton
onSessionChanged: {
// Otherwise the password field loses focus and virtual keyboard
// keystrokes get eaten
userListComponent.mainPasswordBox.forceActiveFocus();
}
Layout.fillHeight: true
containmentMask: Item {
parent: sessionButton
anchors.fill: parent
anchors.leftMargin: virtualKeyboardButton.visible || keyboardButton.visible
? 0 : -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
Item {
Layout.fillWidth: true
}
Battery {}
}
}
Connections {
target: sddm
function onLoginFailed() {
notificationMessage = i18nd("plasma-desktop-sddm-theme", "Login Failed")
footer.enabled = true
mainStack.enabled = true
userListComponent.userList.opacity = 0
rejectPasswordAnimation.start()
}
function onLoginSucceeded() {
//note SDDM will kill the greeter at some random point after this
//there is no certainty any transition will finish, it depends on the time it
//takes to complete the init
mainStack.opacity = 0
footer.opacity = 0
}
}
onNotificationMessageChanged: {
if (notificationMessage) {
notificationResetTimer.start();
}
}
Timer {
id: notificationResetTimer
interval: 3000
onTriggered: notificationMessage = ""
}
}
}