269 lines
8.9 KiB
QML
269 lines
8.9 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import org.kde.plasma.core 2.0 as PlasmaCore
|
|
|
|
/*
|
|
* NexusOS kscreenlocker theme.
|
|
* Mirrors the SDDM NexusOS-QML aesthetic: logo upper-center,
|
|
* clock/date bottom-center, centered password box.
|
|
*
|
|
* kscreenlocker API differences from SDDM:
|
|
* - authenticator.tryUnlock(password) instead of sddm.login(...)
|
|
* - authenticator.failed / authenticator.succeeded signals
|
|
* - No session selector, no power buttons
|
|
* - walletModel, userModel available but not mandatory
|
|
*/
|
|
|
|
Rectangle {
|
|
id: root
|
|
|
|
// kscreenlocker injects these from the surrounding PlasmaShell context
|
|
property bool locked: true
|
|
|
|
readonly property color accentColor: "#8cc63f" // brand_green
|
|
readonly property color bgDark: "#1e1526" // base_bg
|
|
readonly property color textPrimary: "#f2f2f2"
|
|
readonly property color textMuted: "#a8a8a8"
|
|
readonly property color fieldBg: "#1f2225" // surface_bg
|
|
readonly property color fieldBorder: "#4d3461" // menu_border
|
|
readonly property color errorColor: "#da4453"
|
|
|
|
anchors.fill: parent
|
|
color: bgDark
|
|
|
|
// ── Background image (reuses SDDM background asset) ────────────────
|
|
Image {
|
|
anchors.fill: parent
|
|
source: Qt.resolvedUrl("../../assets/background.svg")
|
|
fillMode: Image.PreserveAspectCrop
|
|
smooth: true
|
|
asynchronous: false
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: "#000000"
|
|
opacity: 0.30
|
|
}
|
|
|
|
// ── Logo area ───────────────────────────────────────────────────────
|
|
Item {
|
|
id: logoArea
|
|
width: 100
|
|
height: 100
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: parent.height * 0.10
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: parent.width * 1.6
|
|
height: parent.height * 1.6
|
|
radius: width / 2
|
|
color: accentColor
|
|
opacity: 0.06
|
|
}
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
source: Qt.resolvedUrl("../../assets/logo.png")
|
|
sourceSize: Qt.size(200, 200)
|
|
smooth: true
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: "NexusOS"
|
|
color: textPrimary
|
|
font.family: "Sans"
|
|
font.pixelSize: 26
|
|
font.letterSpacing: 6
|
|
font.weight: Font.Light
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: logoArea.bottom
|
|
anchors.topMargin: 14
|
|
}
|
|
|
|
// ── Password box ────────────────────────────────────────────────────
|
|
Rectangle {
|
|
id: loginBox
|
|
width: 340
|
|
height: loginColumn.height + 56
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: Qt.rgba(30/255, 21/255, 38/255, 0.80)
|
|
radius: 12
|
|
border.color: fieldBorder
|
|
border.width: 1
|
|
|
|
SequentialAnimation {
|
|
id: shakeAnim
|
|
NumberAnimation { target: loginBox; property: "x"; to: loginBox.x - 10; duration: 50 }
|
|
NumberAnimation { target: loginBox; property: "x"; to: loginBox.x + 10; duration: 50 }
|
|
NumberAnimation { target: loginBox; property: "x"; to: loginBox.x - 6; duration: 50 }
|
|
NumberAnimation { target: loginBox; property: "x"; to: loginBox.x + 6; duration: 50 }
|
|
NumberAnimation { target: loginBox; property: "x"; to: loginBox.x; duration: 40 }
|
|
}
|
|
|
|
Column {
|
|
id: loginColumn
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.margins: 28
|
|
spacing: 12
|
|
|
|
// Username label (read-only; kscreenlocker always locks current session)
|
|
Text {
|
|
width: parent.width
|
|
text: kscreenlocker_userName || userModel.data(userModel.index(0, 0), Qt.DisplayRole) || ""
|
|
color: textPrimary
|
|
font.pixelSize: 14
|
|
font.weight: Font.Medium
|
|
horizontalAlignment: Text.AlignHCenter
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
// Password field
|
|
Rectangle {
|
|
width: parent.width
|
|
height: 44
|
|
color: fieldBg
|
|
radius: 6
|
|
border.color: passwordInput.activeFocus ? accentColor : fieldBorder
|
|
border.width: 1
|
|
|
|
TextInput {
|
|
id: passwordInput
|
|
anchors.fill: parent
|
|
anchors.leftMargin: 14
|
|
anchors.rightMargin: 14
|
|
verticalAlignment: TextInput.AlignVCenter
|
|
color: textPrimary
|
|
font.pixelSize: 14
|
|
echoMode: TextInput.Password
|
|
focus: true
|
|
clip: true
|
|
|
|
Keys.onReturnPressed: authenticator.tryUnlock(passwordInput.text)
|
|
Keys.onEnterPressed: authenticator.tryUnlock(passwordInput.text)
|
|
}
|
|
|
|
Text {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 14
|
|
text: "Password"
|
|
color: textMuted
|
|
font.pixelSize: 14
|
|
visible: passwordInput.text.length === 0 && !passwordInput.activeFocus
|
|
}
|
|
}
|
|
|
|
// Error message
|
|
Text {
|
|
id: errorMsg
|
|
width: parent.width
|
|
text: ""
|
|
color: errorColor
|
|
font.pixelSize: 12
|
|
horizontalAlignment: Text.AlignHCenter
|
|
wrapMode: Text.WordWrap
|
|
visible: text !== ""
|
|
}
|
|
|
|
// Unlock button
|
|
Rectangle {
|
|
id: unlockButton
|
|
width: parent.width
|
|
height: 44
|
|
radius: 6
|
|
color: unlockMouse.pressed
|
|
? Qt.darker(accentColor, 1.3)
|
|
: unlockMouse.containsMouse
|
|
? Qt.lighter(accentColor, 1.15)
|
|
: accentColor
|
|
|
|
Behavior on color { ColorAnimation { duration: 150 } }
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "UNLOCK"
|
|
color: "#0a0a00" // text_on_accent
|
|
font.pixelSize: 14
|
|
font.letterSpacing: 3
|
|
font.weight: Font.DemiBold
|
|
}
|
|
|
|
MouseArea {
|
|
id: unlockMouse
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: authenticator.tryUnlock(passwordInput.text)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Authenticator connections ───────────────────────────────────────
|
|
Connections {
|
|
target: authenticator
|
|
|
|
function onFailed() {
|
|
errorMsg.text = "Incorrect password — try again."
|
|
passwordInput.text = ""
|
|
passwordInput.forceActiveFocus()
|
|
shakeAnim.start()
|
|
}
|
|
|
|
function onSucceeded() {
|
|
errorMsg.text = ""
|
|
}
|
|
|
|
function onGraceLockedChanged() {}
|
|
function onMessage(msg) {}
|
|
function onError(err) {
|
|
errorMsg.text = err
|
|
}
|
|
}
|
|
|
|
// ── Clock / date (bottom) ───────────────────────────────────────────
|
|
Column {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: parent.height * 0.05
|
|
spacing: 2
|
|
|
|
Text {
|
|
id: clockText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: textPrimary
|
|
font.family: "Sans"
|
|
font.pixelSize: 42
|
|
font.weight: Font.Thin
|
|
}
|
|
|
|
Text {
|
|
id: dateText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: textMuted
|
|
font.family: "Sans"
|
|
font.pixelSize: 14
|
|
font.letterSpacing: 2
|
|
}
|
|
|
|
Timer {
|
|
interval: 1000
|
|
running: true
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
onTriggered: {
|
|
var d = new Date()
|
|
clockText.text = Qt.formatTime(d, "hh:mm")
|
|
dateText.text = Qt.formatDate(d, "dddd, MMMM d")
|
|
}
|
|
}
|
|
}
|
|
}
|