From ea9b0d2a79d484189a41a68ffcb6698e59a66f2e Mon Sep 17 00:00:00 2001 From: bbedward Date: Sun, 16 Nov 2025 15:05:06 -0500 Subject: [PATCH] powermenu: use consistent new-style on locker + greeter fixes #739 --- quickshell/Common/Theme.qml | 37 +- quickshell/DMSShell.qml | 42 - .../ControlCenter/ControlCenterPopout.qml | 7 + .../Modules/ControlCenter/PowerMenu.qml | 316 ------- quickshell/Modules/Lock/LockPowerMenu.qml | 809 ++++++++++-------- .../KeyboardNavigatedNotificationList.qml | 3 +- .../Center/NotificationCenterPopout.qml | 18 +- quickshell/Widgets/StyledText.qml | 1 + 8 files changed, 500 insertions(+), 733 deletions(-) delete mode 100644 quickshell/Modules/ControlCenter/PowerMenu.qml diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index f399ff78..a2463181 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -421,15 +421,44 @@ Singleton { } return typeof SettingsData !== "undefined" ? SettingsData.cornerRadius : 12 } + + property string fontFamily: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.fontFamily + } + return typeof SettingsData !== "undefined" ? SettingsData.fontFamily : "Inter Variable" + } + + property string monoFontFamily: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.monoFontFamily + } + return typeof SettingsData !== "undefined" ? SettingsData.monoFontFamily : "Fira Code" + } + + property int fontWeight: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.fontWeight + } + return typeof SettingsData !== "undefined" ? SettingsData.fontWeight : Font.Normal + } + + property real fontScale: { + if (typeof SessionData !== "undefined" && SessionData.isGreeterMode && typeof GreetdSettings !== "undefined") { + return GreetdSettings.fontScale + } + return typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0 + } + property real spacingXS: 4 property real spacingS: 8 property real spacingM: 12 property real spacingL: 16 property real spacingXL: 24 - property real fontSizeSmall: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 12 - property real fontSizeMedium: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 14 - property real fontSizeLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 16 - property real fontSizeXLarge: (typeof SettingsData !== "undefined" ? SettingsData.fontScale : 1.0) * 20 + property real fontSizeSmall: fontScale * 12 + property real fontSizeMedium: fontScale * 14 + property real fontSizeLarge: fontScale * 16 + property real fontSizeXLarge: fontScale * 20 property real barHeight: 48 property real iconSize: 24 property real iconSizeSmall: 16 diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 6f1c7b40..feac06ff 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -297,48 +297,6 @@ Item { } } - LazyLoader { - id: powerMenuLoader - - active: false - - PowerMenu { - id: powerMenu - - onPowerActionRequested: (action, title, message) => { - if (SettingsData.powerActionConfirm) { - powerConfirmModalLoader.active = true - if (powerConfirmModalLoader.item) { - powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary - powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {}) - } - } else { - actionApply(action) - } - } - - function actionApply(action) { - switch (action) { - case "logout": - SessionService.logout() - break - case "suspend": - SessionService.suspend() - break - case "hibernate": - SessionService.hibernate() - break - case "reboot": - SessionService.reboot() - break - case "poweroff": - SessionService.poweroff() - break - } - } - } - } - LazyLoader { id: powerConfirmModalLoader diff --git a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml index 9cc6257a..1ce8f329 100644 --- a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml +++ b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml @@ -72,6 +72,13 @@ DankPopout { screen: triggerScreen shouldBeVisible: false + WlrLayershell.keyboardFocus: { + if (!shouldBeVisible) return WlrKeyboardFocus.None + if (powerMenuOpen) return WlrKeyboardFocus.None + if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand + return WlrKeyboardFocus.Exclusive + } + onBackgroundClicked: close() onShouldBeVisibleChanged: { diff --git a/quickshell/Modules/ControlCenter/PowerMenu.qml b/quickshell/Modules/ControlCenter/PowerMenu.qml deleted file mode 100644 index 400ce2f9..00000000 --- a/quickshell/Modules/ControlCenter/PowerMenu.qml +++ /dev/null @@ -1,316 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Widgets -import qs.Common -import qs.Widgets - -PanelWindow { - id: root - - readonly property string powerOptionsText: I18n.tr("Power Options") - readonly property string logOutText: I18n.tr("Log Out") - readonly property string suspendText: I18n.tr("Suspend") - readonly property string rebootText: I18n.tr("Reboot") - readonly property string powerOffText: I18n.tr("Power Off") - - property bool powerMenuVisible: false - signal powerActionRequested(string action, string title, string message) - - visible: powerMenuVisible - implicitWidth: 400 - implicitHeight: 320 - WlrLayershell.layer: WlrLayershell.Overlay - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - color: "transparent" - - anchors { - top: true - left: true - right: true - bottom: true - } - - MouseArea { - anchors.fill: parent - onClicked: { - powerMenuVisible = false - } - } - - Rectangle { - width: Math.min(320, parent.width - Theme.spacingL * 2) - height: 320 // Fixed height to prevent cropping - x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL) - y: Theme.barHeight + Theme.spacingXS - color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) - radius: Theme.cornerRadius - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, - Theme.outline.b, 0.08) - border.width: 0 - opacity: powerMenuVisible ? 1 : 0 - scale: powerMenuVisible ? 1 : 0.85 - - MouseArea { - - anchors.fill: parent - onClicked: { - - } - } - - Column { - anchors.fill: parent - anchors.margins: Theme.spacingL - spacing: Theme.spacingM - - Row { - width: parent.width - - StyledText { - text: root.powerOptionsText - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - - Item { - width: parent.width - 150 - height: 1 - } - - DankActionButton { - iconName: "close" - iconSize: Theme.iconSize - 4 - iconColor: Theme.surfaceText - onClicked: { - powerMenuVisible = false - } - } - } - - Column { - width: parent.width - spacing: Theme.spacingS - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: logoutArea.containsMouse ? Qt.rgba(Theme.primary.r, - Theme.primary.g, - Theme.primary.b, - 0.08) : Qt.rgba( - Theme.surfaceVariant.r, - Theme.surfaceVariant.g, - Theme.surfaceVariant.b, - 0.08) - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "logout" - size: Theme.iconSize - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: root.logOutText - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: logoutArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - powerMenuVisible = false - root.powerActionRequested( - "logout", "Log Out", - "Are you sure you want to log out?") - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: suspendArea.containsMouse ? Qt.rgba(Theme.primary.r, - Theme.primary.g, - Theme.primary.b, - 0.08) : Qt.rgba( - Theme.surfaceVariant.r, - Theme.surfaceVariant.g, - Theme.surfaceVariant.b, - 0.08) - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "bedtime" - size: Theme.iconSize - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: root.suspendText - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: suspendArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - powerMenuVisible = false - root.powerActionRequested( - "suspend", "Suspend", - "Are you sure you want to suspend the system?") - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: rebootArea.containsMouse ? Qt.rgba(Theme.warning.r, - Theme.warning.g, - Theme.warning.b, - 0.08) : Qt.rgba( - Theme.surfaceVariant.r, - Theme.surfaceVariant.g, - Theme.surfaceVariant.b, - 0.08) - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "restart_alt" - size: Theme.iconSize - color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: root.rebootText - font.pixelSize: Theme.fontSizeMedium - color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: rebootArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - powerMenuVisible = false - root.powerActionRequested( - "reboot", "Reboot", - "Are you sure you want to reboot the system?") - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: powerOffArea.containsMouse ? Qt.rgba(Theme.error.r, - Theme.error.g, - Theme.error.b, - 0.08) : Qt.rgba( - Theme.surfaceVariant.r, - Theme.surfaceVariant.g, - Theme.surfaceVariant.b, - 0.08) - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "power_settings_new" - size: Theme.iconSize - color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: root.powerOffText - font.pixelSize: Theme.fontSizeMedium - color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: powerOffArea - - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - powerMenuVisible = false - root.powerActionRequested( - "poweroff", "Power Off", - "Are you sure you want to power off the system?") - } - } - } - } - } - - Behavior on opacity { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing - } - } - - Behavior on scale { - NumberAnimation { - duration: Theme.mediumDuration - easing.type: Theme.emphasizedEasing - } - } - } -} diff --git a/quickshell/Modules/Lock/LockPowerMenu.qml b/quickshell/Modules/Lock/LockPowerMenu.qml index 363c5259..0fdefc1d 100644 --- a/quickshell/Modules/Lock/LockPowerMenu.qml +++ b/quickshell/Modules/Lock/LockPowerMenu.qml @@ -11,25 +11,123 @@ Rectangle { property bool isVisible: false property bool showLogout: true property int selectedIndex: 0 - property int optionCount: { - let count = 0 - if (showLogout) count++ - count++ - if (SessionService.hibernateSupported) count++ - count += 2 - return count - } + property int selectedRow: 0 + property int selectedCol: 0 + property var visibleActions: [] + property int gridColumns: 3 + property int gridRows: 2 + property bool useGridLayout: false signal closed() - function show() { - isVisible = true - selectedIndex = 0 - Qt.callLater(() => { - if (powerMenuFocusScope && powerMenuFocusScope.forceActiveFocus) { - powerMenuFocusScope.forceActiveFocus() - } + function updateVisibleActions() { + const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) + ? SettingsData.powerMenuActions + : ["logout", "suspend", "hibernate", "reboot", "poweroff"] + const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false + let filtered = allActions.filter(action => { + if (action === "hibernate" && !hibernateSupported) return false + if (action === "lock") return false + if (action === "restart") return false + if (action === "logout" && !showLogout) return false + return true }) + + visibleActions = filtered + + useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) + ? SettingsData.powerMenuGridLayout + : false + if (!useGridLayout) return + + const count = visibleActions.length + if (count === 0) { + gridColumns = 1 + gridRows = 1 + return + } + + if (count <= 3) { + gridColumns = 1 + gridRows = count + return + } + + if (count === 4) { + gridColumns = 2 + gridRows = 2 + return + } + + gridColumns = 3 + gridRows = Math.ceil(count / 3) + } + + function getDefaultActionIndex() { + const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) + ? SettingsData.powerMenuDefaultAction + : "suspend" + const index = visibleActions.indexOf(defaultAction) + return index >= 0 ? index : 0 + } + + function getActionAtIndex(index) { + if (index < 0 || index >= visibleActions.length) return "" + return visibleActions[index] + } + + function getActionData(action) { + switch (action) { + case "reboot": + return { "icon": "restart_alt", "label": I18n.tr("Reboot"), "key": "R" } + case "logout": + return { "icon": "logout", "label": I18n.tr("Log Out"), "key": "X" } + case "poweroff": + return { "icon": "power_settings_new", "label": I18n.tr("Power Off"), "key": "P" } + case "suspend": + return { "icon": "bedtime", "label": I18n.tr("Suspend"), "key": "S" } + case "hibernate": + return { "icon": "ac_unit", "label": I18n.tr("Hibernate"), "key": "H" } + default: + return { "icon": "help", "label": action, "key": "?" } + } + } + + function selectOption(action) { + if (!action) return + if (typeof SessionService === "undefined") return + hide() + switch (action) { + case "logout": + SessionService.logout() + break + case "suspend": + SessionService.suspend() + break + case "hibernate": + SessionService.hibernate() + break + case "reboot": + SessionService.reboot() + break + case "poweroff": + SessionService.poweroff() + break + } + } + + function show() { + updateVisibleActions() + const defaultIndex = getDefaultActionIndex() + if (useGridLayout) { + selectedRow = Math.floor(defaultIndex / gridColumns) + selectedCol = defaultIndex % gridColumns + selectedIndex = defaultIndex + } else { + selectedIndex = defaultIndex + } + isVisible = true + Qt.callLater(() => powerMenuFocusScope.forceActiveFocus()) } function hide() { @@ -37,6 +135,148 @@ Rectangle { closed() } + function handleListNavigation(event) { + switch (event.key) { + case Qt.Key_Up: + case Qt.Key_Backtab: + selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length + event.accepted = true + break + case Qt.Key_Down: + case Qt.Key_Tab: + selectedIndex = (selectedIndex + 1) % visibleActions.length + event.accepted = true + break + case Qt.Key_Return: + case Qt.Key_Enter: + selectOption(getActionAtIndex(selectedIndex)) + event.accepted = true + break + case Qt.Key_N: + if (event.modifiers & Qt.ControlModifier) { + selectedIndex = (selectedIndex + 1) % visibleActions.length + event.accepted = true + } + break + case Qt.Key_P: + if (!(event.modifiers & Qt.ControlModifier)) { + selectOption("poweroff") + event.accepted = true + } else { + selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length + event.accepted = true + } + break + case Qt.Key_J: + if (event.modifiers & Qt.ControlModifier) { + selectedIndex = (selectedIndex + 1) % visibleActions.length + event.accepted = true + } + break + case Qt.Key_K: + if (event.modifiers & Qt.ControlModifier) { + selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length + event.accepted = true + } + break + case Qt.Key_R: + selectOption("reboot") + event.accepted = true + break + case Qt.Key_X: + selectOption("logout") + event.accepted = true + break + case Qt.Key_S: + selectOption("suspend") + event.accepted = true + break + case Qt.Key_H: + selectOption("hibernate") + event.accepted = true + break + } + } + + function handleGridNavigation(event) { + switch (event.key) { + case Qt.Key_Left: + selectedCol = (selectedCol - 1 + gridColumns) % gridColumns + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + break + case Qt.Key_Right: + selectedCol = (selectedCol + 1) % gridColumns + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + break + case Qt.Key_Up: + case Qt.Key_Backtab: + selectedRow = (selectedRow - 1 + gridRows) % gridRows + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + break + case Qt.Key_Down: + case Qt.Key_Tab: + selectedRow = (selectedRow + 1) % gridRows + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + break + case Qt.Key_Return: + case Qt.Key_Enter: + selectOption(getActionAtIndex(selectedIndex)) + event.accepted = true + break + case Qt.Key_N: + if (event.modifiers & Qt.ControlModifier) { + selectedCol = (selectedCol + 1) % gridColumns + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + } + break + case Qt.Key_P: + if (!(event.modifiers & Qt.ControlModifier)) { + selectOption("poweroff") + event.accepted = true + } else { + selectedCol = (selectedCol - 1 + gridColumns) % gridColumns + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + } + break + case Qt.Key_J: + if (event.modifiers & Qt.ControlModifier) { + selectedRow = (selectedRow + 1) % gridRows + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + } + break + case Qt.Key_K: + if (event.modifiers & Qt.ControlModifier) { + selectedRow = (selectedRow - 1 + gridRows) % gridRows + selectedIndex = selectedRow * gridColumns + selectedCol + event.accepted = true + } + break + case Qt.Key_R: + selectOption("reboot") + event.accepted = true + break + case Qt.Key_X: + selectOption("logout") + event.accepted = true + break + case Qt.Key_S: + selectOption("suspend") + event.accepted = true + break + case Qt.Key_H: + selectOption("hibernate") + event.accepted = true + break + } + } + anchors.fill: parent color: Qt.rgba(0, 0, 0, 0.5) visible: isVisible @@ -53,102 +293,39 @@ Rectangle { focus: root.isVisible onVisibleChanged: { - if (visible) { - Qt.callLater(() => forceActiveFocus()) - } - } - - Keys.onEscapePressed: { - root.hide() + if (visible) Qt.callLater(() => forceActiveFocus()) } + Keys.onEscapePressed: root.hide() Keys.onPressed: event => { - switch (event.key) { - case Qt.Key_Up: - case Qt.Key_Backtab: - selectedIndex = (selectedIndex - 1 + optionCount) % optionCount - event.accepted = true - break - case Qt.Key_Down: - case Qt.Key_Tab: - selectedIndex = (selectedIndex + 1) % optionCount - event.accepted = true - break - case Qt.Key_Return: - case Qt.Key_Enter: - const actions = [] - if (showLogout) actions.push("logout") - actions.push("suspend") - if (SessionService.hibernateSupported) actions.push("hibernate") - actions.push("reboot", "poweroff") - if (selectedIndex < actions.length) { - const action = actions[selectedIndex] - hide() - switch (action) { - case "logout": - SessionService.logout() - break - case "suspend": - SessionService.suspend() - break - case "hibernate": - SessionService.hibernate() - break - case "reboot": - SessionService.reboot() - break - case "poweroff": - SessionService.poweroff() - break - } - } - event.accepted = true - break - case Qt.Key_N: - if (event.modifiers & Qt.ControlModifier) { - selectedIndex = (selectedIndex + 1) % optionCount - event.accepted = true - } - break - case Qt.Key_P: - if (event.modifiers & Qt.ControlModifier) { - selectedIndex = (selectedIndex - 1 + optionCount) % optionCount - event.accepted = true - } - break - case Qt.Key_J: - if (event.modifiers & Qt.ControlModifier) { - selectedIndex = (selectedIndex + 1) % optionCount - event.accepted = true - } - break - case Qt.Key_K: - if (event.modifiers & Qt.ControlModifier) { - selectedIndex = (selectedIndex - 1 + optionCount) % optionCount - event.accepted = true - } - break + if (useGridLayout) { + handleGridNavigation(event) + } else { + handleListNavigation(event) } } Rectangle { anchors.centerIn: parent - width: 320 - implicitHeight: mainColumn.implicitHeight + Theme.spacingL * 2 - height: implicitHeight + width: useGridLayout + ? Math.min(550, gridColumns * 180 + Theme.spacingS * (gridColumns - 1) + Theme.spacingL * 2) + : 320 + height: contentItem.implicitHeight + Theme.spacingL * 2 radius: Theme.cornerRadius color: Theme.surfaceContainer border.color: Theme.outlineMedium border.width: 1 - Column { - id: mainColumn + Item { + id: contentItem anchors.fill: parent anchors.margins: Theme.spacingL - spacing: Theme.spacingM + implicitHeight: headerRow.height + Theme.spacingM + (useGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) Row { + id: headerRow width: parent.width + height: 30 StyledText { text: I18n.tr("Power Options") @@ -171,289 +348,199 @@ Rectangle { } } - Column { + Grid { + id: buttonGrid + visible: useGridLayout + anchors.top: headerRow.bottom + anchors.topMargin: Theme.spacingM + anchors.horizontalCenter: parent.horizontalCenter + columns: root.gridColumns + columnSpacing: Theme.spacingS + rowSpacing: Theme.spacingS width: parent.width - spacing: Theme.spacingS - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - visible: showLogout - color: { - if (selectedIndex === 0) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - } else if (logoutArea.containsMouse) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) - } else { + Repeater { + model: root.visibleActions + + Rectangle { + required property int index + required property string modelData + + readonly property var actionData: root.getActionData(modelData) + readonly property bool isSelected: root.selectedIndex === index + readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff" + + width: (contentItem.width - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns + height: 100 + radius: Theme.cornerRadius + color: { + if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + if (mouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) } - } - border.color: selectedIndex === 0 ? Theme.primary : "transparent" - border.width: selectedIndex === 0 ? 1 : 0 + border.color: isSelected ? Theme.primary : "transparent" + border.width: isSelected ? 2 : 0 - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM + Column { + anchors.centerIn: parent + spacing: Theme.spacingS - DankIcon { - name: "logout" - size: Theme.iconSize - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter + DankIcon { + name: parent.parent.actionData.icon + size: Theme.iconSize + 8 + color: { + if (parent.parent.showWarning && mouseArea.containsMouse) { + return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning + } + return Theme.surfaceText + } + anchors.horizontalCenter: parent.horizontalCenter + } + + StyledText { + text: parent.parent.actionData.label + font.pixelSize: Theme.fontSizeMedium + color: { + if (parent.parent.showWarning && mouseArea.containsMouse) { + return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning + } + return Theme.surfaceText + } + font.weight: Font.Medium + anchors.horizontalCenter: parent.horizontalCenter + } + + Rectangle { + width: 20 + height: 16 + radius: 4 + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1) + anchors.horizontalCenter: parent.horizontalCenter + + StyledText { + text: parent.parent.parent.actionData.key + font.pixelSize: Theme.fontSizeSmall - 1 + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + font.weight: Font.Medium + anchors.centerIn: parent + } + } } - StyledText { - text: I18n.tr("Log Out") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: logoutArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.hide() - SessionService.logout() - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: { - const suspendIdx = showLogout ? 1 : 0 - if (selectedIndex === suspendIdx) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - } else if (suspendArea.containsMouse) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) - } else { - return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - } - } - border.color: selectedIndex === (showLogout ? 1 : 0) ? Theme.primary : "transparent" - border.width: selectedIndex === (showLogout ? 1 : 0) ? 1 : 0 - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "bedtime" - size: Theme.iconSize - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Suspend") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: suspendArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.hide() - SessionService.suspend() - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: { - const hibernateIdx = showLogout ? 2 : 1 - if (selectedIndex === hibernateIdx) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - } else if (hibernateArea.containsMouse) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) - } else { - return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - } - } - border.color: selectedIndex === (showLogout ? 2 : 1) ? Theme.primary : "transparent" - border.width: selectedIndex === (showLogout ? 2 : 1) ? 1 : 0 - visible: SessionService.hibernateSupported - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "ac_unit" - size: Theme.iconSize - color: Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Hibernate") - font.pixelSize: Theme.fontSizeMedium - color: Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: hibernateArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.hide() - SessionService.hibernate() - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: { - let rebootIdx = showLogout ? 3 : 2 - if (!SessionService.hibernateSupported) rebootIdx-- - if (selectedIndex === rebootIdx) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - } else if (rebootArea.containsMouse) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) - } else { - return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - } - } - border.color: { - let rebootIdx = showLogout ? 3 : 2 - if (!SessionService.hibernateSupported) rebootIdx-- - return selectedIndex === rebootIdx ? Theme.primary : "transparent" - } - border.width: { - let rebootIdx = showLogout ? 3 : 2 - if (!SessionService.hibernateSupported) rebootIdx-- - return selectedIndex === rebootIdx ? 1 : 0 - } - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "restart_alt" - size: Theme.iconSize - color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Reboot") - font.pixelSize: Theme.fontSizeMedium - color: rebootArea.containsMouse ? Theme.warning : Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: rebootArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.hide() - SessionService.reboot() - } - } - } - - Rectangle { - width: parent.width - height: 50 - radius: Theme.cornerRadius - color: { - let powerOffIdx = showLogout ? 4 : 3 - if (!SessionService.hibernateSupported) powerOffIdx-- - if (selectedIndex === powerOffIdx) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) - } else if (powerOffArea.containsMouse) { - return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) - } else { - return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) - } - } - border.color: { - let powerOffIdx = showLogout ? 4 : 3 - if (!SessionService.hibernateSupported) powerOffIdx-- - return selectedIndex === powerOffIdx ? Theme.primary : "transparent" - } - border.width: { - let powerOffIdx = showLogout ? 4 : 3 - if (!SessionService.hibernateSupported) powerOffIdx-- - return selectedIndex === powerOffIdx ? 1 : 0 - } - - Row { - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: "power_settings_new" - size: Theme.iconSize - color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: I18n.tr("Power Off") - font.pixelSize: Theme.fontSizeMedium - color: powerOffArea.containsMouse ? Theme.error : Theme.surfaceText - font.weight: Font.Medium - anchors.verticalCenter: parent.verticalCenter - } - } - - MouseArea { - id: powerOffArea - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - root.hide() - SessionService.poweroff() + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.selectedRow = Math.floor(index / root.gridColumns) + root.selectedCol = index % root.gridColumns + root.selectOption(modelData) + } } } } } - Item { - height: Theme.spacingS + Column { + id: buttonColumn + visible: !useGridLayout + anchors.top: headerRow.bottom + anchors.topMargin: Theme.spacingM + anchors.left: parent.left + anchors.right: parent.right + spacing: Theme.spacingS + + Repeater { + model: root.visibleActions + + Rectangle { + required property int index + required property string modelData + + readonly property var actionData: root.getActionData(modelData) + readonly property bool isSelected: root.selectedIndex === index + readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff" + + width: parent.width + height: 50 + radius: Theme.cornerRadius + color: { + if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) + if (listMouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) + return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) + } + border.color: isSelected ? Theme.primary : "transparent" + border.width: isSelected ? 2 : 0 + + Row { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Theme.spacingM + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + + DankIcon { + name: parent.parent.actionData.icon + size: Theme.iconSize + 4 + color: { + if (parent.parent.showWarning && listMouseArea.containsMouse) { + return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning + } + return Theme.surfaceText + } + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: parent.parent.actionData.label + font.pixelSize: Theme.fontSizeMedium + color: { + if (parent.parent.showWarning && listMouseArea.containsMouse) { + return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning + } + return Theme.surfaceText + } + font.weight: Font.Medium + anchors.verticalCenter: parent.verticalCenter + } + } + + Rectangle { + width: 28 + height: 20 + radius: 4 + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.1) + anchors.right: parent.right + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + + StyledText { + text: parent.parent.actionData.key + font.pixelSize: Theme.fontSizeSmall + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + font.weight: Font.Medium + anchors.centerIn: parent + } + } + + MouseArea { + id: listMouseArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.selectedIndex = index + root.selectOption(modelData) + } + } + } + } } } } } + + Component.onCompleted: updateVisibleActions() } diff --git a/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml b/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml index 1287a819..09a7333e 100644 --- a/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml +++ b/quickshell/Modules/Notifications/Center/KeyboardNavigatedNotificationList.qml @@ -40,7 +40,8 @@ DankListView { NotificationEmptyState { visible: listView.count === 0 - anchors.centerIn: parent + y: 20 + anchors.horizontalCenter: parent.horizontalCenter } onModelChanged: { diff --git a/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml b/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml index 76c7f741..b9efb3eb 100644 --- a/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml +++ b/quickshell/Modules/Notifications/Center/NotificationCenterPopout.qml @@ -111,13 +111,20 @@ DankPopout { implicitHeight: { let baseHeight = Theme.spacingL * 2 baseHeight += cachedHeaderHeight - baseHeight += (notificationSettings.expanded ? notificationSettings.contentHeight : 0) baseHeight += Theme.spacingM * 2 + + const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0 let listHeight = notificationList.listContentHeight if (NotificationService.groupedNotifications.length === 0) { listHeight = 200 } - baseHeight += Math.min(listHeight, 600) + + const maxContentArea = 600 + const availableListSpace = Math.max(200, maxContentArea - settingsHeight) + + baseHeight += settingsHeight + baseHeight += Math.min(listHeight, availableListSpace) + const maxHeight = root.screen ? root.screen.height * 0.8 : Screen.height * 0.8 return Math.max(300, Math.min(baseHeight, maxHeight)) } @@ -198,13 +205,6 @@ DankPopout { showHints: (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false z: 200 } - - Behavior on implicitHeight { - NumberAnimation { - duration: 180 - easing.type: Easing.OutQuart - } - } } } } diff --git a/quickshell/Widgets/StyledText.qml b/quickshell/Widgets/StyledText.qml index 32fa55e4..773922e8 100644 --- a/quickshell/Widgets/StyledText.qml +++ b/quickshell/Widgets/StyledText.qml @@ -39,6 +39,7 @@ Text { elide: Text.ElideRight verticalAlignment: Text.AlignVCenter antialiasing: true + renderType: Text.NativeRendering Behavior on opacity { NumberAnimation {