Files
noctalia-shell/Modules/LockScreen/LockScreen.qml
T
ItsLemmy bbf06e6707 autofmt
2025-10-12 18:35:08 -04:00

1207 lines
44 KiB
QML

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pam
import Quickshell.Services.UPower
import Quickshell.Io
import Quickshell.Widgets
import qs.Commons
import qs.Services
import qs.Widgets
import qs.Modules.Audio
Loader {
id: lockScreen
active: false
Timer {
id: unloadAfterUnlockTimer
interval: 250
repeat: false
onTriggered: lockScreen.active = false
}
function scheduleUnloadAfterUnlock() {
unloadAfterUnlockTimer.start()
}
sourceComponent: Component {
Item {
id: lockContainer
LockContext {
id: lockContext
onUnlocked: {
lockSession.locked = false
lockScreen.scheduleUnloadAfterUnlock()
lockContext.currentText = ""
}
onFailed: {
lockContext.currentText = ""
}
}
WlSessionLock {
id: lockSession
locked: lockScreen.active
WlSessionLockSurface {
readonly property var now: Time.date
Item {
id: batteryIndicator
property var battery: UPower.displayDevice
property bool isReady: battery && battery.ready && battery.isLaptopBattery && battery.isPresent
property real percent: isReady ? (battery.percentage * 100) : 0
property bool charging: isReady ? battery.state === UPowerDeviceState.Charging : false
property bool batteryVisible: isReady && percent > 0
}
Item {
id: keyboardLayout
property string currentLayout: (typeof KeyboardLayoutService !== 'undefined' && KeyboardLayoutService.currentLayout) ? KeyboardLayoutService.currentLayout : "Unknown"
}
Image {
id: lockBgImage
anchors.fill: parent
fillMode: Image.PreserveAspectCrop
source: screen ? WallpaperService.getWallpaper(screen.name) : ""
cache: true
smooth: true
mipmap: false
}
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.alpha(Color.mShadow, 0.8)
}
GradientStop {
position: 0.3
color: Qt.alpha(Color.mShadow, 0.4)
}
GradientStop {
position: 0.7
color: Qt.alpha(Color.mShadow, 0.5)
}
GradientStop {
position: 1.0
color: Qt.alpha(Color.mShadow, 0.9)
}
}
}
// Screen corners for lock screen
Item {
anchors.fill: parent
visible: Settings.data.general.showScreenCorners
property color cornerColor: Settings.data.general.forceBlackScreenCorners ? Qt.rgba(0, 0, 0, 1) : Qt.alpha(Color.mSurface, Settings.data.bar.backgroundOpacity)
property real cornerRadius: Style.screenRadius
property real cornerSize: Style.screenRadius
// Top-left concave corner
Canvas {
anchors.top: parent.top
anchors.left: parent.left
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: false
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = parent.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(width, height, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
}
// Top-right concave corner
Canvas {
anchors.top: parent.top
anchors.right: parent.right
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = parent.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(0, height, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
}
// Bottom-left concave corner
Canvas {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = parent.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(width, 0, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
}
// Bottom-right concave corner
Canvas {
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.cornerSize
height: parent.cornerSize
antialiasing: true
renderTarget: Canvas.FramebufferObject
smooth: true
onPaint: {
const ctx = getContext("2d")
if (!ctx)
return
ctx.reset()
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = parent.cornerColor
ctx.fillRect(0, 0, width, height)
ctx.globalCompositeOperation = "destination-out"
ctx.fillStyle = "#ffffff"
ctx.beginPath()
ctx.arc(0, 0, parent.cornerRadius, 0, 2 * Math.PI)
ctx.fill()
}
onWidthChanged: if (available)
requestPaint()
onHeightChanged: if (available)
requestPaint()
}
}
Item {
anchors.fill: parent
// Time, Date, and User Profile Container
Rectangle {
width: Math.max(500, contentRow.implicitWidth + 32)
height: Math.max(120, contentRow.implicitHeight + 32)
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 100
radius: Style.radiusL
color: Color.mSurface
border.color: Qt.alpha(Color.mOutline, 0.2)
border.width: 1
RowLayout {
id: contentRow
anchors.fill: parent
anchors.margins: 16
spacing: 32
// Left side: Avatar
Rectangle {
Layout.preferredWidth: 70
Layout.preferredHeight: 70
Layout.alignment: Qt.AlignVCenter
radius: width * 0.5
color: Color.transparent
Rectangle {
anchors.fill: parent
radius: parent.radius
color: Color.transparent
border.color: Qt.alpha(Color.mPrimary, 0.8)
border.width: 2
SequentialAnimation on border.color {
loops: Animation.Infinite
ColorAnimation {
to: Qt.alpha(Color.mPrimary, 1.0)
duration: 2000
easing.type: Easing.InOutQuad
}
ColorAnimation {
to: Qt.alpha(Color.mPrimary, 0.8)
duration: 2000
easing.type: Easing.InOutQuad
}
}
}
NImageCircled {
anchors.centerIn: parent
width: 66
height: 66
imagePath: Settings.preprocessPath(Settings.data.general.avatarImage)
fallbackIcon: "person"
SequentialAnimation on scale {
loops: Animation.Infinite
NumberAnimation {
to: 1.02
duration: 4000
easing.type: Easing.InOutQuad
}
NumberAnimation {
to: 1.0
duration: 4000
easing.type: Easing.InOutQuad
}
}
}
}
// Center: User Info Column (left-aligned text)
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
spacing: 2
// Welcome back + Username on one line
NText {
text: I18n.tr("lock-screen.welcome-back") + " " + Quickshell.env("USER") + "!"
pointSize: Style.fontSizeXXL
font.weight: Font.Medium
color: Color.mOnSurface
horizontalAlignment: Text.AlignLeft
}
// Date below
NText {
text: Qt.locale().toString(Time.date, "dddd, MMMM d")
pointSize: Style.fontSizeXL
font.weight: Font.Medium
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignLeft
}
}
// Spacer to push time to the right
Item {
Layout.fillWidth: true
}
Item {
Layout.preferredWidth: 70
Layout.preferredHeight: 70
Layout.alignment: Qt.AlignVCenter
// Seconds circular progress
Canvas {
id: secondsProgress
anchors.fill: parent
property real progress: Time.date.getSeconds() / 60
onProgressChanged: requestPaint()
Connections {
target: Time
function onDateChanged() {
const total = Time.date.getSeconds() * 1000 + Time.date.getMilliseconds()
secondsProgress.progress = total / 60000
}
}
onPaint: {
var ctx = getContext("2d")
var centerX = width / 2
var centerY = height / 2
var radius = Math.min(width, height) / 2 - 3
ctx.reset()
// Background circle
ctx.beginPath()
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI)
ctx.lineWidth = 2.5
ctx.strokeStyle = Qt.alpha(Color.mOnSurface, 0.15)
ctx.stroke()
// Progress arc
ctx.beginPath()
ctx.arc(centerX, centerY, radius, -Math.PI / 2, -Math.PI / 2 + progress * 2 * Math.PI)
ctx.lineWidth = 2.5
ctx.strokeStyle = Color.mPrimary
ctx.lineCap = "round"
ctx.stroke()
}
}
// Digital clock
ColumnLayout {
anchors.centerIn: parent
spacing: 0
NText {
text: {
var t = Settings.data.location.use12hourFormat ? Qt.locale().toString(Time.date, "hh AP") : Qt.locale().toString(Time.date, "HH")
return t
}
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
}
NText {
text: Qt.formatTime(Time.date, "mm")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
}
}
}
}
}
// Error notification
Rectangle {
width: 450
height: 60
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 300
radius: 30
color: Color.mError
border.color: Color.mError
border.width: 1
visible: lockContext.showFailure && lockContext.errorMessage
opacity: visible ? 1.0 : 0.0
RowLayout {
anchors.centerIn: parent
spacing: 10
NIcon {
icon: "alert-circle"
pointSize: Style.fontSizeL
color: Color.mOnError
}
NText {
text: lockContext.errorMessage || "Authentication failed"
color: Color.mOnError
pointSize: Style.fontSizeL
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
}
}
Behavior on opacity {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
}
// Compact status indicators container (compact mode only)
Rectangle {
width: {
var hasBattery = UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
var hasKeyboard = keyboardLayout.currentLayout !== "Unknown"
if (hasBattery && hasKeyboard) {
return 200
} else if (hasBattery || hasKeyboard) {
return 120
} else {
return 0
}
}
height: 40
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 96 + (Settings.data.general.compactLockScreen ? 116 : 220)
topLeftRadius: Style.radiusL
topRightRadius: Style.radiusL
color: Color.mSurface
visible: Settings.data.general.compactLockScreen && ((UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent) || keyboardLayout.currentLayout !== "Unknown")
RowLayout {
anchors.centerIn: parent
spacing: 16
// Battery indicator
RowLayout {
spacing: 6
visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
NIcon {
icon: BatteryService.getIcon(Math.round(UPower.displayDevice.percentage * 100), UPower.displayDevice.state === UPowerDeviceState.Charging, true)
pointSize: Style.fontSizeM
color: UPower.displayDevice.state === UPowerDeviceState.Charging ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
font.weight: Font.Medium
}
}
// Keyboard layout indicator
RowLayout {
spacing: 6
visible: keyboardLayout.currentLayout !== "Unknown"
NIcon {
icon: "keyboard"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
NText {
text: keyboardLayout.currentLayout
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
font.weight: Font.Medium
elide: Text.ElideRight
}
}
}
}
// Bottom container with weather, password input and controls
Rectangle {
width: 750
height: Settings.data.general.compactLockScreen ? 120 : 220
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 100
radius: Style.radiusL
color: Color.mSurface
ColumnLayout {
anchors.fill: parent
anchors.margins: 14
spacing: 14
// Weather section
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 65
spacing: 18
visible: !Settings.data.general.compactLockScreen && LocationService.coordinatesReady && LocationService.data.weather !== null
// Media widget with visualizer
Rectangle {
Layout.preferredWidth: 220
Layout.preferredHeight: 50
radius: 25
color: Color.transparent
clip: true
visible: MediaService.currentPlayer && MediaService.canPlay
Loader {
anchors.fill: parent
anchors.margins: 4
active: Settings.data.audio.visualizerType === "linear"
z: 0
sourceComponent: LinearSpectrum {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
}
}
Loader {
anchors.fill: parent
anchors.margins: 4
active: Settings.data.audio.visualizerType === "mirrored"
z: 0
sourceComponent: MirroredSpectrum {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
}
}
Loader {
anchors.fill: parent
anchors.margins: 4
active: Settings.data.audio.visualizerType === "wave"
z: 0
sourceComponent: WaveSpectrum {
anchors.fill: parent
values: CavaService.values
fillColor: Color.mPrimary
opacity: 0.4
}
}
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 8
z: 1
Rectangle {
Layout.preferredWidth: 34
Layout.preferredHeight: 34
radius: width * 0.5
color: Color.transparent
clip: true
NImageCircled {
anchors.fill: parent
anchors.margins: 2
imagePath: MediaService.trackArtUrl
fallbackIcon: "disc"
fallbackIconSize: Style.fontSizeM
borderColor: Color.mOutline
borderWidth: Math.max(1, Style.borderS)
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
NText {
text: MediaService.trackTitle || "No media"
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurface
Layout.fillWidth: true
elide: Text.ElideRight
}
NText {
text: MediaService.trackArtist || ""
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}
}
Rectangle {
Layout.preferredWidth: 1
Layout.fillHeight: true
Layout.rightMargin: 4
color: Qt.alpha(Color.mOutline, 0.3)
visible: MediaService.currentPlayer && MediaService.canPlay
}
Item {
Layout.preferredWidth: Style.marginM
visible: !(MediaService.currentPlayer && MediaService.canPlay)
}
// Current weather
RowLayout {
Layout.preferredWidth: 180
spacing: 8
NIcon {
Layout.alignment: Qt.AlignVCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.current_weather.weathercode)
pointSize: Style.fontSizeXXXL
color: Color.mPrimary
}
ColumnLayout {
Layout.fillWidth: true
spacing: 2
RowLayout {
Layout.fillWidth: true
spacing: 12
NText {
text: {
var temp = LocationService.data.weather.current_weather.temperature
var suffix = "C"
if (Settings.data.location.useFahrenheit) {
temp = LocationService.celsiusToFahrenheit(temp)
suffix = "F"
}
temp = Math.round(temp)
return temp + "°" + suffix
}
pointSize: Style.fontSizeXL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: {
var wind = LocationService.data.weather.current_weather.windspeed
var unit = "km/h"
if (Settings.data.location.useFahrenheit) {
wind = wind * 0.621371 // Convert km/h to mph
unit = "mph"
}
wind = Math.round(wind)
return wind + " " + unit
}
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
font.weight: Font.Normal
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
NText {
text: Settings.data.location.name.split(",")[0]
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
NText {
text: (LocationService.data.weather.current && LocationService.data.weather.current.relativehumidity_2m) ? LocationService.data.weather.current.relativehumidity_2m + "% humidity" : ""
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
}
}
// 3-day forecast
RowLayout {
Layout.preferredWidth: 260
Layout.rightMargin: 8
spacing: 4
Repeater {
model: 3
delegate: ColumnLayout {
Layout.fillWidth: true
spacing: 3
NText {
text: Qt.locale().toString(now, "ddd")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
NIcon {
Layout.alignment: Qt.AlignHCenter
icon: LocationService.weatherSymbolFromCode(LocationService.data.weather.daily.weathercode[index])
pointSize: Style.fontSizeXL
color: Color.mOnSurfaceVariant
}
NText {
text: {
var max = LocationService.data.weather.daily.temperature_2m_max[index]
var min = LocationService.data.weather.daily.temperature_2m_min[index]
if (Settings.data.location.useFahrenheit) {
max = LocationService.celsiusToFahrenheit(max)
min = LocationService.celsiusToFahrenheit(min)
}
max = Math.round(max)
min = Math.round(min)
return max + "°/" + min + "°"
}
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
}
}
}
}
// Battery and Keyboard Layout (full mode only)
ColumnLayout {
Layout.preferredWidth: 60
spacing: 8
// Battery
RowLayout {
spacing: 4
visible: UPower.displayDevice && UPower.displayDevice.ready && UPower.displayDevice.isPresent
NIcon {
icon: BatteryService.getIcon(Math.round(UPower.displayDevice.percentage * 100), UPower.displayDevice.state === UPowerDeviceState.Charging, true)
pointSize: Style.fontSizeM
color: UPower.displayDevice.state === UPowerDeviceState.Charging ? Color.mPrimary : Color.mOnSurfaceVariant
}
NText {
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
font.weight: Font.Medium
elide: Text.ElideRight
Layout.fillWidth: true
}
}
// Keyboard Layout
RowLayout {
spacing: 4
visible: keyboardLayout.currentLayout !== "Unknown"
NIcon {
icon: "keyboard"
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
NText {
text: keyboardLayout.currentLayout
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
font.weight: Font.Medium
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
// Password input
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
Layout.preferredWidth: Style.marginM
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 48
radius: 24
color: Color.mSurface
border.color: passwordInput.activeFocus ? Color.mPrimary : Qt.alpha(Color.mOutline, 0.3)
border.width: passwordInput.activeFocus ? 2 : 1
Row {
anchors.left: parent.left
anchors.leftMargin: 18
anchors.verticalCenter: parent.verticalCenter
spacing: 14
NIcon {
icon: "lock"
pointSize: Style.fontSizeL
color: passwordInput.activeFocus ? Color.mPrimary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
// Hidden input that receives actual text
TextInput {
id: passwordInput
width: 0
height: 0
visible: false
enabled: !lockContext.unlockInProgress
font.pointSize: Style.fontSizeM
color: Color.mPrimary
echoMode: TextInput.Password
passwordCharacter: "•"
passwordMaskDelay: 0
text: lockContext.currentText
onTextChanged: lockContext.currentText = text
Keys.onPressed: function (event) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
lockContext.tryUnlock()
}
}
Component.onCompleted: forceActiveFocus()
}
Row {
spacing: 0
Rectangle {
width: 2
height: 20
color: Color.mPrimary
visible: passwordInput.activeFocus && passwordInput.text.length === 0
anchors.verticalCenter: parent.verticalCenter
SequentialAnimation on opacity {
loops: Animation.Infinite
running: passwordInput.activeFocus && passwordInput.text.length === 0
NumberAnimation {
to: 0
duration: 530
}
NumberAnimation {
to: 1
duration: 530
}
}
}
Row {
spacing: 6
visible: passwordInput.text.length > 0
anchors.verticalCenter: parent.verticalCenter
Repeater {
model: passwordInput.text.length
NIcon {
icon: "circle-filled"
pointSize: Style.fontSizeS
color: Color.mPrimary
opacity: 1.0
}
}
}
Rectangle {
width: 2
height: 20
color: Color.mPrimary
visible: passwordInput.activeFocus && passwordInput.text.length > 0
anchors.verticalCenter: parent.verticalCenter
SequentialAnimation on opacity {
loops: Animation.Infinite
running: passwordInput.activeFocus && passwordInput.text.length > 0
NumberAnimation {
to: 0
duration: 530
}
NumberAnimation {
to: 1
duration: 530
}
}
}
}
}
Rectangle {
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
width: 36
height: 36
radius: width * 0.5
color: submitButtonArea.containsMouse ? Color.mPrimary : Qt.alpha(Color.mPrimary, 0.8)
border.color: Color.mPrimary
border.width: 1
enabled: !lockContext.unlockInProgress
NIcon {
anchors.centerIn: parent
icon: "arrow-forward"
pointSize: Style.fontSizeM
color: Color.mOnPrimary
}
MouseArea {
id: submitButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: lockContext.tryUnlock()
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Item {
Layout.preferredWidth: Style.marginM
}
}
// System control buttons
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
spacing: 10
Item {
Layout.preferredWidth: Style.marginM
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
radius: Settings.data.general.compactLockScreen ? 18 : 24
color: logoutButtonArea.containsMouse ? Color.mTertiary : "transparent"
border.color: Color.mOutline
border.width: 1
RowLayout {
anchors.centerIn: parent
spacing: 6
NIcon {
icon: "logout"
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.logout")
color: logoutButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
MouseArea {
id: logoutButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: CompositorService.logout()
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
radius: Settings.data.general.compactLockScreen ? 18 : 24
color: suspendButtonArea.containsMouse ? Color.mTertiary : "transparent"
border.color: Color.mOutline
border.width: 1
RowLayout {
anchors.centerIn: parent
spacing: 6
NIcon {
icon: "suspend"
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.suspend")
color: suspendButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
MouseArea {
id: suspendButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: CompositorService.suspend()
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
radius: Settings.data.general.compactLockScreen ? 18 : 24
color: rebootButtonArea.containsMouse ? Color.mTertiary : "transparent"
border.color: Color.mOutline
border.width: 1
RowLayout {
anchors.centerIn: parent
spacing: 6
NIcon {
icon: "reboot"
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.reboot")
color: rebootButtonArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
MouseArea {
id: rebootButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: CompositorService.reboot()
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48
radius: Settings.data.general.compactLockScreen ? 18 : 24
color: shutdownButtonArea.containsMouse ? Color.mError : "transparent"
border.color: shutdownButtonArea.containsMouse ? Color.mError : Color.mOutline
border.width: 1
RowLayout {
anchors.centerIn: parent
spacing: 6
NIcon {
icon: "shutdown"
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL
color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant
}
NText {
text: I18n.tr("session-menu.shutdown")
color: shutdownButtonArea.containsMouse ? Color.mOnError : Color.mOnSurfaceVariant
pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM
font.weight: Font.Medium
}
}
MouseArea {
id: shutdownButtonArea
anchors.fill: parent
hoverEnabled: true
onClicked: CompositorService.shutdown()
}
Behavior on color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
Behavior on border.color {
ColorAnimation {
duration: 200
easing.type: Easing.OutCubic
}
}
}
Item {
Layout.preferredWidth: Style.marginM
}
}
}
}
}
}
}
}
}
}