import QtQuick import QtQuick.Controls import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Services.Pam import Quickshell.Services.UPower import Quickshell.Wayland import Quickshell.Widgets import qs.Commons import qs.Services.Compositor import qs.Services.Hardware import qs.Services.Keyboard import qs.Services.Location import qs.Services.Media import qs.Services.System import qs.Services.UI import qs.Widgets import qs.Widgets.AudioSpectrum Loader { id: root active: false Component.onCompleted: { // Register with panel service PanelService.lockScreen = this; } Timer { id: unloadAfterUnlockTimer interval: 250 repeat: false onTriggered: root.active = false } function scheduleUnloadAfterUnlock() { unloadAfterUnlockTimer.start(); } sourceComponent: Component { Item { id: lockContainer LockContext { id: lockContext onUnlocked: { lockSession.locked = false; root.scheduleUnloadAfterUnlock(); lockContext.currentText = ""; } onFailed: { lockContext.currentText = ""; } } WlSessionLock { id: lockSession locked: root.active WlSessionLockSurface { readonly property var now: Time.now 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: KeyboardLayoutService.currentLayout } 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 ? Color.black : 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") + " " + HostService.displayName + "!" pointSize: Style.fontSizeXXL font.weight: Font.Medium color: Color.mOnSurface horizontalAlignment: Text.AlignLeft } // Date below NText { text: { var lang = I18n.locale.name.split("_")[0]; var formats = { "de": "dddd, d. MMMM", "es": "dddd, d 'de' MMMM", "fr": "dddd d MMMM", "pt": "dddd, d 'de' MMMM", "zh": "yyyy年M月d日 dddd", "uk": "dddd, d MMMM", "tr": "dddd, d MMMM" }; return I18n.locale.toString(Time.now, formats[lang] || "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 } // Clock NClock { now: Time.now clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital" Layout.preferredWidth: 70 Layout.preferredHeight: 70 Layout.alignment: Qt.AlignVCenter backgroundColor: Color.mSurface clockColor: Color.mOnSurface secondHandColor: Color.mPrimary hoursFontSize: Style.fontSizeL minutesFontSize: Style.fontSizeL } } } // Error notification Rectangle { width: 450 height: 60 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: (Settings.data.general.compactLockScreen ? 240 : 320) * Style.uiScaleRatio 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 { id: bottomContainer height: Settings.data.general.compactLockScreen ? 120 : 220 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 100 radius: Style.radiusL color: Color.mSurface // Measure text widths to determine minimum button width (for container width calculation) Item { id: buttonRowTextMeasurer visible: false property real iconSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL property real fontSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM property real spacing: 6 property real padding: 18 // Approximate horizontal padding per button // Measure all button text widths Text { id: logoutText text: I18n.tr("session-menu.logout") font.pointSize: buttonRowTextMeasurer.fontSize font.weight: Font.Medium } Text { id: suspendText text: I18n.tr("session-menu.suspend") font.pointSize: buttonRowTextMeasurer.fontSize font.weight: Font.Medium } Text { id: hibernateText text: I18n.tr("session-menu.hibernate") font.pointSize: buttonRowTextMeasurer.fontSize font.weight: Font.Medium } Text { id: rebootText text: I18n.tr("session-menu.reboot") font.pointSize: buttonRowTextMeasurer.fontSize font.weight: Font.Medium } Text { id: shutdownText text: I18n.tr("session-menu.shutdown") font.pointSize: buttonRowTextMeasurer.fontSize font.weight: Font.Medium } // Calculate maximum width needed property real maxTextWidth: Math.max(logoutText.implicitWidth, Math.max(suspendText.implicitWidth, Math.max(hibernateText.implicitWidth, Math.max(rebootText.implicitWidth, shutdownText.implicitWidth)))) property real minButtonWidth: maxTextWidth + iconSize + spacing + padding } // Calculate minimum width based on button requirements // Button row needs: margins + 5 buttons + 4 spacings + margins // Plus ColumnLayout margins (14 on each side = 28 total) // Add extra buffer to ensure password input has proper padding property real minButtonRowWidth: buttonRowTextMeasurer.minButtonWidth > 0 ? (5 * buttonRowTextMeasurer.minButtonWidth) + 40 + (2 * Style.marginM) + 28 + (2 * Style.marginM) : 750 width: Math.max(750, minButtonRowWidth) ColumnLayout { anchors.fill: parent anchors.margins: 14 spacing: 14 // Top info row RowLayout { Layout.fillWidth: true Layout.preferredHeight: 65 spacing: 18 visible: !Settings.data.general.compactLockScreen // Media widget with visualizer Rectangle { Layout.preferredWidth: 220 // Expand to take remaining space when weather is hidden Layout.fillWidth: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) 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: NLinearSpectrum { 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: NMirroredSpectrum { 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: NWaveSpectrum { 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: 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 { visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null 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 { visible: Settings.data.location.weatherEnabled && LocationService.data.weather !== null Layout.preferredWidth: 260 Layout.rightMargin: 8 spacing: 4 Repeater { model: 3 delegate: ColumnLayout { Layout.fillWidth: true spacing: 3 NText { text: { var weatherDate = new Date(LocationService.data.weather.daily.time[index].replace(/-/g, "/")); return I18n.locale.toString(weatherDate, "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 } } } } Item { Layout.fillWidth: true visible: !(Settings.data.location.weatherEnabled && LocationService.data.weather !== null) Layout.preferredWidth: visible ? 1 : 0 } // Battery and Keyboard Layout (full mode only) ColumnLayout { Layout.preferredWidth: 60 Layout.alignment: Qt.AlignRight | Qt.AlignVCenter 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 property bool passwordVisible: false 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: parent.parent.passwordVisible ? TextInput.Normal : 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 } } } // Password display - show dots or actual text based on passwordVisible Item { width: Math.min(passwordDisplayContent.width, 550) height: 20 visible: passwordInput.text.length > 0 && !parent.parent.parent.passwordVisible anchors.verticalCenter: parent.verticalCenter clip: true Row { id: passwordDisplayContent spacing: 6 anchors.verticalCenter: parent.verticalCenter Repeater { model: passwordInput.text.length NIcon { icon: "circle-filled" pointSize: Style.fontSizeS color: Color.mPrimary opacity: 1.0 } } } } NText { text: passwordInput.text color: Color.mPrimary pointSize: Style.fontSizeM font.weight: Font.Medium visible: passwordInput.text.length > 0 && parent.parent.parent.passwordVisible anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight width: Math.min(implicitWidth, 550) } 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 } } } } } // Eye button to toggle password visibility Rectangle { anchors.right: submitButton.left anchors.rightMargin: 4 anchors.verticalCenter: parent.verticalCenter width: 36 height: 36 radius: width * 0.5 color: eyeButtonArea.containsMouse ? Qt.alpha(Color.mOnSurface, 0.1) : "transparent" visible: passwordInput.text.length > 0 enabled: !lockContext.unlockInProgress NIcon { anchors.centerIn: parent icon: parent.parent.passwordVisible ? "eye-off" : "eye" pointSize: Style.fontSizeM color: Color.mOnSurfaceVariant } MouseArea { id: eyeButtonArea anchors.fill: parent hoverEnabled: true onClicked: parent.parent.passwordVisible = !parent.parent.passwordVisible } Behavior on color { ColorAnimation { duration: 200 easing.type: Easing.OutCubic } } } // Submit button Rectangle { id: submitButton 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.minimumWidth: buttonRowTextMeasurer.minButtonWidth Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 radius: Settings.data.general.compactLockScreen ? 18 : 24 color: logoutButtonArea.containsMouse ? Color.mHover : "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.mOnHover : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.logout") pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM color: logoutButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant 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.minimumWidth: buttonRowTextMeasurer.minButtonWidth Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 radius: Settings.data.general.compactLockScreen ? 18 : 24 color: suspendButtonArea.containsMouse ? Color.mHover : "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.mOnHover : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.suspend") pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM color: suspendButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant 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.minimumWidth: buttonRowTextMeasurer.minButtonWidth Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 radius: Settings.data.general.compactLockScreen ? 18 : 24 color: hibernateButtonArea.containsMouse ? Color.mHover : "transparent" border.color: Color.mOutline border.width: 1 RowLayout { anchors.centerIn: parent spacing: 6 NIcon { icon: "hibernate" pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeM : Style.fontSizeL color: hibernateButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.hibernate") pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM color: hibernateButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant font.weight: Font.Medium } } MouseArea { id: hibernateButtonArea anchors.fill: parent hoverEnabled: true onClicked: CompositorService.hibernate() } 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.minimumWidth: buttonRowTextMeasurer.minButtonWidth Layout.preferredHeight: Settings.data.general.compactLockScreen ? 36 : 48 radius: Settings.data.general.compactLockScreen ? 18 : 24 color: rebootButtonArea.containsMouse ? Color.mHover : "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.mOnHover : Color.mOnSurfaceVariant } NText { text: I18n.tr("session-menu.reboot") pointSize: Settings.data.general.compactLockScreen ? Style.fontSizeS : Style.fontSizeM color: rebootButtonArea.containsMouse ? Color.mOnHover : Color.mOnSurfaceVariant 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.minimumWidth: buttonRowTextMeasurer.minButtonWidth 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 } } } } } } } } } }