Rework LockScreen auth logic
This commit is contained in:
parent
4d8cf2207d
commit
76626dc8da
2 changed files with 770 additions and 1007 deletions
92
Modules/LockScreen/LockContext.qml
Normal file
92
Modules/LockScreen/LockContext.qml
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Services.Pam
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
id: root
|
||||||
|
signal unlocked()
|
||||||
|
signal failed()
|
||||||
|
|
||||||
|
property string currentText: ""
|
||||||
|
property bool unlockInProgress: false
|
||||||
|
property bool showFailure: false
|
||||||
|
property string errorMessage: ""
|
||||||
|
property bool pamAvailable: typeof PamContext !== "undefined"
|
||||||
|
|
||||||
|
onCurrentTextChanged: {
|
||||||
|
if (currentText !== "") {
|
||||||
|
showFailure = false;
|
||||||
|
errorMessage = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryUnlock() {
|
||||||
|
if (!pamAvailable) {
|
||||||
|
errorMessage = "PAM not available";
|
||||||
|
showFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentText === "") {
|
||||||
|
errorMessage = "Password required";
|
||||||
|
showFailure = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.unlockInProgress = true;
|
||||||
|
errorMessage = "";
|
||||||
|
showFailure = false;
|
||||||
|
|
||||||
|
console.log("Starting PAM authentication for user:", pam.user);
|
||||||
|
pam.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
PamContext {
|
||||||
|
id: pam
|
||||||
|
config: "login"
|
||||||
|
user: Quickshell.env("USER")
|
||||||
|
|
||||||
|
onPamMessage: {
|
||||||
|
console.log("PAM message:", message, "isError:", messageIsError, "responseRequired:", responseRequired);
|
||||||
|
|
||||||
|
if (messageIsError) {
|
||||||
|
errorMessage = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (responseRequired) {
|
||||||
|
console.log("Responding to PAM with password");
|
||||||
|
respond(root.currentText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponseRequiredChanged: {
|
||||||
|
console.log("Response required changed:", responseRequired);
|
||||||
|
if (responseRequired && root.unlockInProgress) {
|
||||||
|
console.log("Automatically responding to PAM");
|
||||||
|
respond(root.currentText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCompleted: {
|
||||||
|
console.log("PAM completed with result:", result);
|
||||||
|
if (result === PamResult.Success) {
|
||||||
|
console.log("Authentication successful");
|
||||||
|
root.unlocked();
|
||||||
|
} else {
|
||||||
|
console.log("Authentication failed");
|
||||||
|
errorMessage = "Authentication failed";
|
||||||
|
showFailure = true;
|
||||||
|
root.failed();
|
||||||
|
}
|
||||||
|
root.unlockInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onError: {
|
||||||
|
console.log("PAM error:", error, "message:", message);
|
||||||
|
errorMessage = message || "Authentication error";
|
||||||
|
showFailure = true;
|
||||||
|
root.unlockInProgress = false;
|
||||||
|
root.failed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,163 +17,67 @@ Loader {
|
||||||
id: lockScreen
|
id: lockScreen
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
// Log state changes to help debug lock screen issues
|
|
||||||
onActiveChanged: {
|
|
||||||
Logger.log("LockScreen", "State changed:", active)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow a small grace period after unlocking so the compositor releases the lock surfaces
|
|
||||||
Timer {
|
Timer {
|
||||||
id: unloadAfterUnlockTimer
|
id: unloadAfterUnlockTimer
|
||||||
interval: 250
|
interval: 250
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
Logger.log("LockScreen", "Unload timer triggered - deactivating")
|
|
||||||
lockScreen.active = false
|
lockScreen.active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleUnloadAfterUnlock() {
|
function scheduleUnloadAfterUnlock() {
|
||||||
Logger.log("LockScreen", "Scheduling unload after unlock")
|
|
||||||
unloadAfterUnlockTimer.start()
|
unloadAfterUnlockTimer.start()
|
||||||
}
|
}
|
||||||
sourceComponent: Component {
|
|
||||||
WlSessionLock {
|
|
||||||
id: lock
|
|
||||||
|
|
||||||
// Tie session lock to loader visibility
|
sourceComponent: Component {
|
||||||
|
Item {
|
||||||
|
id: lockContainer
|
||||||
|
|
||||||
|
// Create the lock context
|
||||||
|
LockContext {
|
||||||
|
id: lockContext
|
||||||
|
onUnlocked: {
|
||||||
|
lockSession.locked = false
|
||||||
|
lockScreen.scheduleUnloadAfterUnlock()
|
||||||
|
lockContext.currentText = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WlSessionLock {
|
||||||
|
id: lockSession
|
||||||
locked: lockScreen.active
|
locked: lockScreen.active
|
||||||
|
|
||||||
property string errorMessage: ""
|
|
||||||
property bool authenticating: false
|
|
||||||
property string password: ""
|
|
||||||
property bool pamAvailable: typeof PamContext !== "undefined"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function unlockAttempt() {
|
|
||||||
Logger.log("LockScreen", "Unlock attempt started")
|
|
||||||
|
|
||||||
// Real PAM authentication
|
|
||||||
if (!pamAvailable) {
|
|
||||||
lock.errorMessage = "PAM authentication not available."
|
|
||||||
Logger.log("LockScreen", "PAM not available")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!lock.password) {
|
|
||||||
lock.errorMessage = "Password required."
|
|
||||||
Logger.log("LockScreen", "No password entered")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.log("LockScreen", "Starting PAM authentication")
|
|
||||||
lock.authenticating = true
|
|
||||||
lock.errorMessage = ""
|
|
||||||
|
|
||||||
Logger.log("LockScreen", "About to create PAM context with userName:", Quickshell.env("USER"))
|
|
||||||
var pam = Qt.createQmlObject(
|
|
||||||
'import Quickshell.Services.Pam; PamContext { config: "login"; user: "' + Quickshell.env("USER") + '" }',
|
|
||||||
lock)
|
|
||||||
Logger.log("LockScreen", "PamContext created", pam)
|
|
||||||
|
|
||||||
pam.onCompleted.connect(function (result) {
|
|
||||||
Logger.log("LockScreen", "PAM completed with result:", result)
|
|
||||||
lock.authenticating = false
|
|
||||||
if (result === PamResult.Success) {
|
|
||||||
Logger.log("LockScreen", "Authentication successful, unlocking")
|
|
||||||
// First release the Wayland session lock, then unload after a short delay
|
|
||||||
lock.locked = false
|
|
||||||
lockScreen.scheduleUnloadAfterUnlock()
|
|
||||||
lock.password = ""
|
|
||||||
lock.errorMessage = ""
|
|
||||||
} else {
|
|
||||||
Logger.log("LockScreen", "Authentication failed")
|
|
||||||
lock.errorMessage = "Authentication failed."
|
|
||||||
lock.password = ""
|
|
||||||
}
|
|
||||||
pam.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
pam.onError.connect(function (error) {
|
|
||||||
Logger.log("LockScreen", "PAM error:", error)
|
|
||||||
lock.authenticating = false
|
|
||||||
lock.errorMessage = pam.message || "Authentication error."
|
|
||||||
lock.password = ""
|
|
||||||
pam.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
pam.onPamMessage.connect(function () {
|
|
||||||
Logger.log("LockScreen", "PAM message:", pam.message, "isError:", pam.messageIsError)
|
|
||||||
if (pam.messageIsError) {
|
|
||||||
lock.errorMessage = pam.message
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
pam.onResponseRequiredChanged.connect(function () {
|
|
||||||
Logger.log("LockScreen", "PAM response required:", pam.responseRequired)
|
|
||||||
if (pam.responseRequired && lock.authenticating) {
|
|
||||||
Logger.log("LockScreen", "Responding to PAM with password")
|
|
||||||
pam.respond(lock.password)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var started = pam.start()
|
|
||||||
Logger.log("LockScreen", "PAM start result:", started)
|
|
||||||
}
|
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
// Battery indicator component
|
|
||||||
|
|
||||||
// WlSessionLockSurface provides a screen variable for the current screen.
|
|
||||||
// Also we use a different scaling algorithm based on the resolution, as the design is full screen.
|
|
||||||
readonly property real scaling: ScalingService.dynamicScale(screen)
|
readonly property real scaling: ScalingService.dynamicScale(screen)
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: batteryIndicator
|
id: batteryIndicator
|
||||||
|
|
||||||
// Import UPower for battery data
|
|
||||||
property var battery: UPower.displayDevice
|
property var battery: UPower.displayDevice
|
||||||
property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
|
||||||
property real percent: isReady ? (battery.percentage * 100) : 0
|
property real percent: isReady ? (battery.percentage * 100) : 0
|
||||||
property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
|
property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
|
||||||
property bool batteryVisible: isReady && percent > 0
|
property bool batteryVisible: isReady && percent > 0
|
||||||
|
|
||||||
// Choose icon based on charge and charging state
|
|
||||||
function getIcon() {
|
function getIcon() {
|
||||||
if (!batteryVisible)
|
if (!batteryVisible) return ""
|
||||||
return ""
|
if (charging) return "battery_android_bolt"
|
||||||
|
if (percent >= 95) return "battery_android_full"
|
||||||
if (charging)
|
if (percent >= 85) return "battery_android_6"
|
||||||
return "battery_android_bolt"
|
if (percent >= 70) return "battery_android_5"
|
||||||
|
if (percent >= 55) return "battery_android_4"
|
||||||
if (percent >= 95)
|
if (percent >= 40) return "battery_android_3"
|
||||||
return "battery_android_full"
|
if (percent >= 25) return "battery_android_2"
|
||||||
|
if (percent >= 10) return "battery_android_1"
|
||||||
// Hardcoded battery symbols
|
if (percent >= 0) return "battery_android_0"
|
||||||
if (percent >= 85)
|
|
||||||
return "battery_android_6"
|
|
||||||
if (percent >= 70)
|
|
||||||
return "battery_android_5"
|
|
||||||
if (percent >= 55)
|
|
||||||
return "battery_android_4"
|
|
||||||
if (percent >= 40)
|
|
||||||
return "battery_android_3"
|
|
||||||
if (percent >= 25)
|
|
||||||
return "battery_android_2"
|
|
||||||
if (percent >= 10)
|
|
||||||
return "battery_android_1"
|
|
||||||
if (percent >= 0)
|
|
||||||
return "battery_android_0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard layout indicator component
|
|
||||||
Item {
|
Item {
|
||||||
id: keyboardLayout
|
id: keyboardLayout
|
||||||
|
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
|
||||||
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined'
|
|
||||||
&& KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wallpaper image
|
|
||||||
Image {
|
Image {
|
||||||
id: lockBgImage
|
id: lockBgImage
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -184,40 +88,23 @@ Loader {
|
||||||
mipmap: false
|
mipmap: false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blurred background
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Color.transparent
|
color: Color.transparent
|
||||||
|
|
||||||
// Simple blur effect
|
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.smooth: true
|
layer.smooth: true
|
||||||
layer.samples: 4
|
layer.samples: 4
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animated gradient overlay
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
GradientStop {
|
GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.6) }
|
||||||
position: 0.0
|
GradientStop { position: 0.3; color: Qt.rgba(0, 0, 0, 0.3) }
|
||||||
color: Qt.rgba(0, 0, 0, 0.6)
|
GradientStop { position: 0.7; color: Qt.rgba(0, 0, 0, 0.4) }
|
||||||
}
|
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.7) }
|
||||||
GradientStop {
|
|
||||||
position: 0.3
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.3)
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 0.7
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.4)
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 1.0
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.7)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtle animated particles
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: 20
|
model: 20
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -230,24 +117,16 @@ Loader {
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
SequentialAnimation on opacity {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 0.8; duration: 2000 + Math.random() * 3000 }
|
||||||
to: 0.8
|
NumberAnimation { to: 0.1; duration: 2000 + Math.random() * 3000 }
|
||||||
duration: 2000 + Math.random() * 3000
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.1
|
|
||||||
duration: 2000 + Math.random() * 3000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main content - Centered design
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
// Top section - Time, date, and user info
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
@ -255,7 +134,6 @@ Loader {
|
||||||
anchors.topMargin: 80 * scaling
|
anchors.topMargin: 80 * scaling
|
||||||
spacing: 40 * scaling
|
spacing: 40 * scaling
|
||||||
|
|
||||||
// Time display - Large and prominent with pulse animation
|
|
||||||
Column {
|
Column {
|
||||||
spacing: Style.marginXS * scaling
|
spacing: Style.marginXS * scaling
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
@ -272,16 +150,8 @@ Loader {
|
||||||
|
|
||||||
SequentialAnimation on scale {
|
SequentialAnimation on scale {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.02; duration: 2000; easing.type: Easing.InOutQuad }
|
||||||
to: 1.02
|
NumberAnimation { to: 1.0; duration: 2000; easing.type: Easing.InOutQuad }
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 1.0
|
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,12 +167,10 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User section with animated avatar
|
|
||||||
Column {
|
Column {
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
// Animated avatar with glow effect or audio visualizer
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 108 * scaling
|
width: 108 * scaling
|
||||||
height: 108 * scaling
|
height: 108 * scaling
|
||||||
|
|
@ -313,31 +181,25 @@ Loader {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
z: 10
|
z: 10
|
||||||
|
|
||||||
// Circular audio visualizer when music is playing
|
|
||||||
Loader {
|
Loader {
|
||||||
active: MediaService.isPlaying && Settings.data.audio.visualizerType == "linear"
|
active: MediaService.isPlaying && Settings.data.audio.visualizerType == "linear"
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 160 * scaling
|
width: 160 * scaling
|
||||||
height: 160 * scaling
|
height: 160 * scaling
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
Repeater {
|
Repeater {
|
||||||
model: CavaService.values.length
|
model: CavaService.values.length
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property real linearAngle: (index / CavaService.values.length) * 2 * Math.PI
|
property real linearAngle: (index / CavaService.values.length) * 2 * Math.PI
|
||||||
property real linearRadius: 70 * scaling
|
property real linearRadius: 70 * scaling
|
||||||
property real linearBarLength: Math.max(2, CavaService.values[index] * 30 * scaling)
|
property real linearBarLength: Math.max(2, CavaService.values[index] * 30 * scaling)
|
||||||
property real linearBarWidth: 3 * scaling
|
property real linearBarWidth: 3 * scaling
|
||||||
|
|
||||||
width: linearBarWidth
|
width: linearBarWidth
|
||||||
height: linearBarLength
|
height: linearBarLength
|
||||||
color: Color.mPrimary
|
color: Color.mPrimary
|
||||||
radius: linearBarWidth * 0.5
|
radius: linearBarWidth * 0.5
|
||||||
|
|
||||||
x: parent.width * 0.5 + Math.cos(linearAngle) * linearRadius - width * 0.5
|
x: parent.width * 0.5 + Math.cos(linearAngle) * linearRadius - width * 0.5
|
||||||
y: parent.height * 0.5 + Math.sin(linearAngle) * linearRadius - height * 0.5
|
y: parent.height * 0.5 + Math.sin(linearAngle) * linearRadius - height * 0.5
|
||||||
|
|
||||||
transform: Rotation {
|
transform: Rotation {
|
||||||
origin.x: linearBarWidth * 0.5
|
origin.x: linearBarWidth * 0.5
|
||||||
origin.y: linearBarLength * 0.5
|
origin.y: linearBarLength * 0.5
|
||||||
|
|
@ -353,28 +215,21 @@ Loader {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 160 * scaling
|
width: 160 * scaling
|
||||||
height: 160 * scaling
|
height: 160 * scaling
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
Repeater {
|
Repeater {
|
||||||
model: CavaService.values.length * 2
|
model: CavaService.values.length * 2
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length
|
property int mirroredValueIndex: index < CavaService.values.length ? index : (CavaService.values.length * 2 - 1 - index)
|
||||||
* 2 - 1 - index)
|
|
||||||
property real mirroredAngle: (index / (CavaService.values.length * 2)) * 2 * Math.PI
|
property real mirroredAngle: (index / (CavaService.values.length * 2)) * 2 * Math.PI
|
||||||
property real mirroredRadius: 70 * scaling
|
property real mirroredRadius: 70 * scaling
|
||||||
property real mirroredBarLength: Math.max(2,
|
property real mirroredBarLength: Math.max(2, CavaService.values[mirroredValueIndex] * 30 * scaling)
|
||||||
CavaService.values[mirroredValueIndex] * 30 * scaling)
|
|
||||||
property real mirroredBarWidth: 3 * scaling
|
property real mirroredBarWidth: 3 * scaling
|
||||||
|
|
||||||
width: mirroredBarWidth
|
width: mirroredBarWidth
|
||||||
height: mirroredBarLength
|
height: mirroredBarLength
|
||||||
color: Color.mPrimary
|
color: Color.mPrimary
|
||||||
radius: mirroredBarWidth * 0.5
|
radius: mirroredBarWidth * 0.5
|
||||||
|
|
||||||
x: parent.width * 0.5 + Math.cos(mirroredAngle) * mirroredRadius - width * 0.5
|
x: parent.width * 0.5 + Math.cos(mirroredAngle) * mirroredRadius - width * 0.5
|
||||||
y: parent.height * 0.5 + Math.sin(mirroredAngle) * mirroredRadius - height * 0.5
|
y: parent.height * 0.5 + Math.sin(mirroredAngle) * mirroredRadius - height * 0.5
|
||||||
|
|
||||||
transform: Rotation {
|
transform: Rotation {
|
||||||
origin.x: mirroredBarWidth * 0.5
|
origin.x: mirroredBarWidth * 0.5
|
||||||
origin.y: mirroredBarLength * 0.5
|
origin.y: mirroredBarLength * 0.5
|
||||||
|
|
@ -390,65 +245,46 @@ Loader {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 160 * scaling
|
width: 160 * scaling
|
||||||
height: 160 * scaling
|
height: 160 * scaling
|
||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
Canvas {
|
Canvas {
|
||||||
id: waveCanvas
|
id: waveCanvas
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
|
|
||||||
onPaint: {
|
onPaint: {
|
||||||
var ctx = getContext("2d")
|
var ctx = getContext("2d")
|
||||||
ctx.reset()
|
ctx.reset()
|
||||||
|
if (CavaService.values.length === 0) return
|
||||||
if (CavaService.values.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.strokeStyle = Color.mPrimary
|
ctx.strokeStyle = Color.mPrimary
|
||||||
ctx.lineWidth = 2 * scaling
|
ctx.lineWidth = 2 * scaling
|
||||||
ctx.lineCap = "round"
|
ctx.lineCap = "round"
|
||||||
|
|
||||||
var centerX = width * 0.5
|
var centerX = width * 0.5
|
||||||
var centerY = height * 0.5
|
var centerY = height * 0.5
|
||||||
var baseRadius = 60 * scaling
|
var baseRadius = 60 * scaling
|
||||||
var maxAmplitude = 20 * scaling
|
var maxAmplitude = 20 * scaling
|
||||||
|
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
|
|
||||||
for (var i = 0; i <= CavaService.values.length; i++) {
|
for (var i = 0; i <= CavaService.values.length; i++) {
|
||||||
var index = i % CavaService.values.length
|
var index = i % CavaService.values.length
|
||||||
var angle = (i / CavaService.values.length) * 2 * Math.PI
|
var angle = (i / CavaService.values.length) * 2 * Math.PI
|
||||||
var amplitude = CavaService.values[index] * maxAmplitude
|
var amplitude = CavaService.values[index] * maxAmplitude
|
||||||
var radius = baseRadius + amplitude
|
var radius = baseRadius + amplitude
|
||||||
|
|
||||||
var x = centerX + Math.cos(angle) * radius
|
var x = centerX + Math.cos(angle) * radius
|
||||||
var y = centerY + Math.sin(angle) * radius
|
var y = centerY + Math.sin(angle) * radius
|
||||||
|
if (i === 0) ctx.moveTo(x, y)
|
||||||
if (i === 0) {
|
else ctx.lineTo(x, y)
|
||||||
ctx.moveTo(x, y)
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(x, y)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ctx.closePath()
|
ctx.closePath()
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 16 // ~60 FPS
|
interval: 16
|
||||||
running: true
|
running: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: waveCanvas.requestPaint()
|
||||||
waveCanvas.requestPaint()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glow effect when no music is playing
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width + 24 * scaling
|
width: parent.width + 24 * scaling
|
||||||
|
|
@ -459,19 +295,10 @@ Loader {
|
||||||
border.width: Math.max(1, Style.borderM * scaling)
|
border.width: Math.max(1, Style.borderM * scaling)
|
||||||
z: -1
|
z: -1
|
||||||
visible: !MediaService.isPlaying
|
visible: !MediaService.isPlaying
|
||||||
|
|
||||||
SequentialAnimation on scale {
|
SequentialAnimation on scale {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.1; duration: 1500; easing.type: Easing.InOutQuad }
|
||||||
to: 1.1
|
NumberAnimation { to: 1.0; duration: 1500; easing.type: Easing.InOutQuad }
|
||||||
duration: 1500
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 1.0
|
|
||||||
duration: 1500
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,7 +310,6 @@ Loader {
|
||||||
fallbackIcon: "person"
|
fallbackIcon: "person"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hover animation
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
@ -492,28 +318,23 @@ Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on scale {
|
Behavior on scale {
|
||||||
NumberAnimation {
|
NumberAnimation { duration: Style.animationFast; easing.type: Easing.OutBack }
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutBack
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Centered terminal section
|
|
||||||
Item {
|
Item {
|
||||||
width: 720 * scaling
|
width: 720 * scaling
|
||||||
height: 280 * scaling
|
height: 280 * scaling
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
anchors.verticalCenterOffset: 50 * scaling
|
||||||
|
|
||||||
// Futuristic Terminal-Style Input
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 280 * scaling
|
height: 280 * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
// Terminal background with scanlines
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: terminalBackground
|
id: terminalBackground
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
@ -522,7 +343,6 @@ Loader {
|
||||||
border.color: Color.mPrimary
|
border.color: Color.mPrimary
|
||||||
border.width: Math.max(1, Style.borderM * scaling)
|
border.width: Math.max(1, Style.borderM * scaling)
|
||||||
|
|
||||||
// Scanline effect
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: 20
|
model: 20
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -531,22 +351,14 @@ Loader {
|
||||||
color: Color.applyOpacity(Color.mPrimary, "1A")
|
color: Color.applyOpacity(Color.mPrimary, "1A")
|
||||||
y: index * 10 * scaling
|
y: index * 10 * scaling
|
||||||
opacity: Style.opacityMedium
|
opacity: Style.opacityMedium
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
SequentialAnimation on opacity {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 0.6; duration: 2000 + Math.random() * 1000 }
|
||||||
to: 0.6
|
NumberAnimation { to: 0.1; duration: 2000 + Math.random() * 1000 }
|
||||||
duration: 2000 + Math.random() * 1000
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.1
|
|
||||||
duration: 2000 + Math.random() * 1000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal header
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 40 * scaling
|
height: 40 * scaling
|
||||||
|
|
@ -571,17 +383,14 @@ Loader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Battery indicator
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Style.marginS * scaling
|
spacing: Style.marginS * scaling
|
||||||
visible: batteryIndicator.batteryVisible
|
visible: batteryIndicator.batteryVisible
|
||||||
|
|
||||||
NIcon {
|
NIcon {
|
||||||
text: batteryIndicator.getIcon()
|
text: batteryIndicator.getIcon()
|
||||||
font.pointSize: Style.fontSizeM * scaling
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
color: batteryIndicator.charging ? Color.mPrimary : Color.mOnSurface
|
||||||
}
|
}
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: Math.round(batteryIndicator.percent) + "%"
|
text: Math.round(batteryIndicator.percent) + "%"
|
||||||
color: Color.mOnSurface
|
color: Color.mOnSurface
|
||||||
|
|
@ -591,10 +400,8 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard layout indicator
|
|
||||||
Row {
|
Row {
|
||||||
spacing: Style.marginS * scaling
|
spacing: Style.marginS * scaling
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
text: keyboardLayout.currentLayout
|
text: keyboardLayout.currentLayout
|
||||||
color: Color.mOnSurface
|
color: Color.mOnSurface
|
||||||
|
|
@ -602,7 +409,6 @@ Loader {
|
||||||
font.pointSize: Style.fontSizeM * scaling
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
font.weight: Style.fontWeightBold
|
font.weight: Style.fontWeightBold
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
NIcon {
|
||||||
text: "keyboard_alt"
|
text: "keyboard_alt"
|
||||||
font.pointSize: Style.fontSizeM * scaling
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
|
|
@ -612,7 +418,6 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal content area
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
@ -622,7 +427,6 @@ Loader {
|
||||||
anchors.topMargin: 70 * scaling
|
anchors.topMargin: 70 * scaling
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
|
|
||||||
// Welcome back typing effect
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
|
|
@ -660,7 +464,6 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command line with integrated password input
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Style.marginM * scaling
|
spacing: Style.marginM * scaling
|
||||||
|
|
@ -680,7 +483,6 @@ Loader {
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integrated password input (invisible, just for functionality)
|
|
||||||
TextInput {
|
TextInput {
|
||||||
id: passwordInput
|
id: passwordInput
|
||||||
width: 0
|
width: 0
|
||||||
|
|
@ -693,16 +495,14 @@ Loader {
|
||||||
passwordCharacter: "*"
|
passwordCharacter: "*"
|
||||||
passwordMaskDelay: 0
|
passwordMaskDelay: 0
|
||||||
|
|
||||||
text: lock.password
|
text: lockContext.currentText
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
lock.password = text
|
lockContext.currentText = text
|
||||||
// Terminal typing sound effect (visual)
|
|
||||||
typingEffect.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
lock.unlockAttempt()
|
lockContext.tryUnlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -711,7 +511,6 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visual password display with integrated cursor
|
|
||||||
NText {
|
NText {
|
||||||
id: asterisksText
|
id: asterisksText
|
||||||
text: "*".repeat(passwordInput.text.length)
|
text: "*".repeat(passwordInput.text.length)
|
||||||
|
|
@ -720,25 +519,13 @@ Loader {
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
visible: passwordInput.activeFocus
|
visible: passwordInput.activeFocus
|
||||||
|
|
||||||
// Typing effect animation
|
|
||||||
SequentialAnimation {
|
SequentialAnimation {
|
||||||
id: typingEffect
|
id: typingEffect
|
||||||
NumberAnimation {
|
NumberAnimation { target: passwordInput; property: "scale"; to: 1.01; duration: 50 }
|
||||||
target: passwordInput
|
NumberAnimation { target: passwordInput; property: "scale"; to: 1.0; duration: 50 }
|
||||||
property: "scale"
|
|
||||||
to: 1.01
|
|
||||||
duration: 50
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: passwordInput
|
|
||||||
property: "scale"
|
|
||||||
to: 1.0
|
|
||||||
duration: 50
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blinking cursor positioned right after the asterisks
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 8 * scaling
|
width: 8 * scaling
|
||||||
height: 20 * scaling
|
height: 20 * scaling
|
||||||
|
|
@ -749,41 +536,36 @@ Loader {
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
SequentialAnimation on opacity {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.0; duration: 500 }
|
||||||
to: 1.0
|
NumberAnimation { to: 0.0; duration: 500 }
|
||||||
duration: 500
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.0
|
|
||||||
duration: 500
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status messages
|
|
||||||
NText {
|
NText {
|
||||||
text: lock.authenticating ? "Authenticating..." : (lock.errorMessage !== "" ? "Authentication failed." : "")
|
text: {
|
||||||
color: lock.authenticating ? Color.mPrimary : (lock.errorMessage !== "" ? Color.mError : Color.transparent)
|
if (lockContext.unlockInProgress) return "Authenticating..."
|
||||||
|
if (lockContext.showFailure && lockContext.errorMessage) return lockContext.errorMessage
|
||||||
|
if (lockContext.showFailure) return "Authentication failed."
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
color: {
|
||||||
|
if (lockContext.unlockInProgress) return Color.mPrimary
|
||||||
|
if (lockContext.showFailure) return Color.mError
|
||||||
|
return Color.transparent
|
||||||
|
}
|
||||||
font.family: "DejaVu Sans Mono"
|
font.family: "DejaVu Sans Mono"
|
||||||
font.pointSize: Style.fontSizeL * scaling
|
font.pointSize: Style.fontSizeL * scaling
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
SequentialAnimation on opacity {
|
||||||
running: lock.authenticating
|
running: lockContext.unlockInProgress
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.0; duration: 800 }
|
||||||
to: 1.0
|
NumberAnimation { to: 0.5; duration: 800 }
|
||||||
duration: 800
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.5
|
|
||||||
duration: 800
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute button
|
|
||||||
Row {
|
Row {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
Layout.bottomMargin: -10 * scaling
|
Layout.bottomMargin: -10 * scaling
|
||||||
|
|
@ -794,11 +576,11 @@ Loader {
|
||||||
color: executeButtonArea.containsMouse ? Color.mPrimary : Color.applyOpacity(Color.mPrimary, "33")
|
color: executeButtonArea.containsMouse ? Color.mPrimary : Color.applyOpacity(Color.mPrimary, "33")
|
||||||
border.color: Color.mPrimary
|
border.color: Color.mPrimary
|
||||||
border.width: Math.max(1, Style.borderS * scaling)
|
border.width: Math.max(1, Style.borderS * scaling)
|
||||||
enabled: !lock.authenticating
|
enabled: !lockContext.unlockInProgress
|
||||||
|
|
||||||
NText {
|
NText {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: lock.authenticating ? "EXECUTING" : "EXECUTE"
|
text: lockContext.unlockInProgress ? "EXECUTING" : "EXECUTE"
|
||||||
color: executeButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
color: executeButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||||
font.family: Settings.data.ui.fontFixed
|
font.family: Settings.data.ui.fontFixed
|
||||||
font.pointSize: Style.fontSizeM * scaling
|
font.pointSize: Style.fontSizeM * scaling
|
||||||
|
|
@ -809,47 +591,31 @@ Loader {
|
||||||
id: executeButtonArea
|
id: executeButtonArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onClicked: lock.unlockAttempt()
|
onClicked: {
|
||||||
|
lockContext.tryUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
SequentialAnimation on scale {
|
SequentialAnimation on scale {
|
||||||
running: executeButtonArea.containsMouse
|
running: executeButtonArea.containsMouse
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.05; duration: Style.animationFast; easing.type: Easing.OutCubic }
|
||||||
to: 1.05
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SequentialAnimation on scale {
|
SequentialAnimation on scale {
|
||||||
running: !executeButtonArea.containsMouse
|
running: !executeButtonArea.containsMouse
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.0; duration: Style.animationFast; easing.type: Easing.OutCubic }
|
||||||
to: 1.0
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processing animation
|
|
||||||
SequentialAnimation on scale {
|
SequentialAnimation on scale {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
running: lock.authenticating
|
running: lockContext.unlockInProgress
|
||||||
NumberAnimation {
|
NumberAnimation { to: 1.02; duration: 600; easing.type: Easing.InOutQuad }
|
||||||
to: 1.02
|
NumberAnimation { to: 1.0; duration: 600; easing.type: Easing.InOutQuad }
|
||||||
duration: 600
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 1.0
|
|
||||||
duration: 600
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal glow effect
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
|
|
@ -860,194 +626,98 @@ Loader {
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
SequentialAnimation on opacity {
|
||||||
loops: Animation.Infinite
|
loops: Animation.Infinite
|
||||||
NumberAnimation {
|
NumberAnimation { to: 0.6; duration: 2000; easing.type: Easing.InOutQuad }
|
||||||
to: 0.6
|
NumberAnimation { to: 0.2; duration: 2000; easing.type: Easing.InOutQuad }
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
to: 0.2
|
|
||||||
duration: 2000
|
|
||||||
easing.type: Easing.InOutQuad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enhanced power buttons with hover effects
|
// Power buttons at bottom
|
||||||
Row {
|
Row {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.margins: 50 * scaling
|
anchors.margins: 50 * scaling
|
||||||
spacing: 20 * scaling
|
spacing: 20 * scaling
|
||||||
|
|
||||||
// Shutdown with enhanced styling
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 64 * scaling
|
width: 60 * scaling
|
||||||
height: 64 * scaling
|
height: 60 * scaling
|
||||||
radius: Style.radiusL * scaling
|
radius: width * 0.5
|
||||||
color: shutdownArea.containsMouse ? Color.applyOpacity(Color.mError,
|
color: powerButtonArea.containsMouse ? Color.mError : Color.applyOpacity(Color.mError, "33")
|
||||||
"DD") : Color.applyOpacity(Color.mError, "22")
|
|
||||||
border.color: Color.mError
|
border.color: Color.mError
|
||||||
border.width: Math.max(1, Style.borderM * scaling)
|
border.width: Math.max(1, Style.borderM * scaling)
|
||||||
|
|
||||||
// Glow effect
|
NIcon {
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width + 10 * scaling
|
text: "power_settings_new"
|
||||||
height: parent.height + 10 * scaling
|
font.pointSize: Style.fontSizeXL * scaling
|
||||||
radius: width * 0.5
|
color: powerButtonArea.containsMouse ? Color.mOnError : Color.mError
|
||||||
color: Color.transparent
|
|
||||||
opacity: shutdownArea.containsMouse ? 1 : 0
|
|
||||||
z: -1
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: shutdownArea
|
id: powerButtonArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
CompositorService.shutdown()
|
CompositorService.shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "power_settings_new"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: shutdownArea.containsMouse ? Color.mOnPrimary : Color.mError
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scale: shutdownArea.containsMouse ? 1.1 : 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reboot with enhanced styling
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 64 * scaling
|
width: 60 * scaling
|
||||||
height: 64 * scaling
|
height: 60 * scaling
|
||||||
radius: Style.radiusL * scaling
|
radius: width * 0.5
|
||||||
color: rebootArea.containsMouse ? Color.applyOpacity(Color.mPrimary,
|
color: restartButtonArea.containsMouse ? Color.mPrimary : Color.applyOpacity(Color.mPrimary, "33")
|
||||||
"DD") : Color.applyOpacity(Color.mPrimary, "22")
|
|
||||||
border.color: Color.mPrimary
|
border.color: Color.mPrimary
|
||||||
border.width: Math.max(1, Style.borderM * scaling)
|
border.width: Math.max(1, Style.borderM * scaling)
|
||||||
|
|
||||||
// Glow effect
|
NIcon {
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width + 10 * scaling
|
text: "restart_alt"
|
||||||
height: parent.height + 10 * scaling
|
font.pointSize: Style.fontSizeXL * scaling
|
||||||
radius: width * 0.5
|
color: restartButtonArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
||||||
color: Color.transparent
|
|
||||||
opacity: rebootArea.containsMouse ? 1 : 0
|
|
||||||
z: -1
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationMedium
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: rebootArea
|
id: restartButtonArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
CompositorService.reboot()
|
CompositorService.reboot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "refresh"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: rebootArea.containsMouse ? Color.mOnPrimary : Color.mPrimary
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationMedium
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scale: rebootArea.containsMouse ? 1.1 : 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logout with enhanced styling
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 64 * scaling
|
width: 60 * scaling
|
||||||
height: 64 * scaling
|
height: 60 * scaling
|
||||||
radius: Style.radiusL * scaling
|
radius: width * 0.5
|
||||||
color: logoutArea.containsMouse ? Color.applyOpacity(Color.mSecondary,
|
color: suspendButtonArea.containsMouse ? Color.mSecondary : Color.applyOpacity(Color.mSecondary, "33")
|
||||||
"DD") : Color.applyOpacity(Color.mSecondary, "22")
|
|
||||||
border.color: Color.mSecondary
|
border.color: Color.mSecondary
|
||||||
border.width: Math.max(1, Style.borderM * scaling)
|
border.width: Math.max(1, Style.borderM * scaling)
|
||||||
|
|
||||||
// Glow effect
|
NIcon {
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width + 10 * scaling
|
text: "bedtime"
|
||||||
height: parent.height + 10 * scaling
|
font.pointSize: Style.fontSizeXL * scaling
|
||||||
radius: width * 0.5
|
color: suspendButtonArea.containsMouse ? Color.mOnSecondary : Color.mSecondary
|
||||||
color: Color.transparent
|
|
||||||
opacity: logoutArea.containsMouse ? 1 : 0
|
|
||||||
z: -1
|
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Style.animationMedium
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: logoutArea
|
id: suspendButtonArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
CompositorService.logout()
|
CompositorService.suspend()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NIcon {
|
|
||||||
text: "exit_to_app"
|
|
||||||
font.pointSize: Style.fontSizeXXXL * scaling
|
|
||||||
color: logoutArea.containsMouse ? Color.mOnPrimary : Color.mSecondary
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Style.animationFast
|
|
||||||
easing.type: Easing.OutCubic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scale: logoutArea.containsMouse ? 1.1 : 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timer for updating time
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
running: true
|
running: true
|
||||||
|
|
@ -1061,3 +731,4 @@ Loader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue