From 97ec64ff97ad76e0644220f87674e8980a42990d Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 29 Sep 2026 01:39:58 -0400 Subject: [PATCH] refactor(cc / blur): restore card background on quick controls & unify blur popouts on Dash/CC --- .../Modules/ControlCenter/CcMetrics.qml | 1 - .../ControlCenter/Components/CcDetailPage.qml | 10 +- .../Components/WidgetConfigOverlay.qml | 182 ++++--------- .../ControlCenter/ControlCenterContent.qml | 5 +- .../ControlCenter/Details/BluetoothDetail.qml | 4 +- .../ControlCenter/Details/NetworkDetail.qml | 6 +- .../Modules/ControlCenter/Widgets/CcMenu.qml | 244 +++--------------- .../ControlCenter/Widgets/CcSheetDialog.qml | 12 + .../ControlCenter/Widgets/QuickActions.qml | 17 +- .../Modules/DankDash/DankDashPopout.qml | 3 +- .../Modules/DankDash/DashPageActions.qml | 4 +- .../Modules/DankDash/DashWidgetGrid.qml | 5 +- quickshell/Modules/DankDash/OverviewTab.qml | 4 + quickshell/Modules/DankDash/WeatherTab.qml | 2 + .../Activities/ControlCenterExpanded.qml | 1 + .../DankIsland/Activities/DashTabFace.qml | 3 +- .../DankIsland/Activities/WeatherExpanded.qml | 1 + .../Modules/Plugins/DashTabComponent.qml | 8 + quickshell/Widgets/BackdropBlur.qml | 1 + quickshell/Widgets/DankContextMenu.qml | 36 ++- 20 files changed, 189 insertions(+), 360 deletions(-) diff --git a/quickshell/Modules/ControlCenter/CcMetrics.qml b/quickshell/Modules/ControlCenter/CcMetrics.qml index 227be7f4a..eab740756 100644 --- a/quickshell/Modules/ControlCenter/CcMetrics.qml +++ b/quickshell/Modules/ControlCenter/CcMetrics.qml @@ -116,7 +116,6 @@ Singleton { readonly property int overlayZ: 10000 readonly property real popupEnterScale: 0.92 readonly property color dialogColor: Theme.foregroundColor(Theme.hostSurface) - readonly property int backdropBlurRadius: 32 readonly property bool hideCoveredContent: BlurService.enabled && Theme.connectedSurfaceBlurEnabled && dialogColor.a < 1 readonly property color tileActiveColor: Theme.ccTileActiveBg diff --git a/quickshell/Modules/ControlCenter/Components/CcDetailPage.qml b/quickshell/Modules/ControlCenter/Components/CcDetailPage.qml index 3acccd609..ba476b6a8 100644 --- a/quickshell/Modules/ControlCenter/Components/CcDetailPage.qml +++ b/quickshell/Modules/ControlCenter/Components/CcDetailPage.qml @@ -17,6 +17,7 @@ FocusScope { property var model: null property string screenName: "" property string screenModel: "" + property var transientSurfaceTracker: null property real topInset: 0 property real minimumContentHeight: 0 property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius) @@ -295,11 +296,6 @@ FocusScope { } } } - - Item { - id: menuOverlay - anchors.fill: panel - } } MouseArea { @@ -347,7 +343,7 @@ FocusScope { Component { id: networkComponent NetworkDetail { - menuParent: menuOverlay + transientSurfaceTracker: root.transientSurfaceTracker transitioning: root.transitioning } } @@ -355,7 +351,7 @@ FocusScope { Component { id: bluetoothComponent BluetoothDetail { - menuParent: menuOverlay + transientSurfaceTracker: root.transientSurfaceTracker } } diff --git a/quickshell/Modules/ControlCenter/Components/WidgetConfigOverlay.qml b/quickshell/Modules/ControlCenter/Components/WidgetConfigOverlay.qml index 642ba7a3f..46b0fde6c 100644 --- a/quickshell/Modules/ControlCenter/Components/WidgetConfigOverlay.qml +++ b/quickshell/Modules/ControlCenter/Components/WidgetConfigOverlay.qml @@ -1,8 +1,7 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects -import Quickshell.Widgets +import Quickshell import qs.Common import qs.Modules.ControlCenter import qs.Modules.ControlCenter.Widgets @@ -16,12 +15,9 @@ import qs.Widgets Item { id: root - property Item backdrop: null property int widgetIndex: -1 - property real anchorX: 0 - property real anchorY: 0 - property real anchorWidth: 0 - property real anchorHeight: 0 + property var transientSurfaceTracker: null + property Item _anchor: null readonly property var widgetData: { if (widgetIndex < 0) @@ -37,21 +33,38 @@ Item { readonly property bool isQuickActions: widgetId === "quickActions" readonly property var quickActions: isQuickActions ? WidgetUtils.quickActions(widgetData) : [] - visible: widgetIndex >= 0 - z: CcMetrics.overlayZ + visible: widgetIndex >= 0 || contextMenu.renderActive function open(index, data, anchorItem) { - const pos = anchorItem.mapToItem(root, 0, 0); - anchorX = pos.x; - anchorY = pos.y; - anchorWidth = anchorItem.width; - anchorHeight = anchorItem.height; widgetIndex = index; - focusScope.forceActiveFocus(); + _anchor = anchorItem; + // Placement needs the final menu height, which settles after widgetIndex propagates. + Qt.callLater(() => { + const window = root.QsWindow.window; + const screen = window?.screen; + if (root.widgetIndex !== index || !anchorItem || !screen) + return; + const pos = anchorItem.mapToGlobal(0, 0); + const x = pos.x - screen.x; + const y = pos.y - screen.y; + const menuX = I18n.isRtl ? x : x + anchorItem.width - contextMenu.effectiveMenuWidth; + const aboveY = () => y - contextMenu.effectiveMenuHeight - Theme.spacingS; + if (aboveY() < Theme.spacingS) { + contextMenu.open(screen, menuX, y + anchorItem.height + Theme.spacingS, false); + return; + } + contextMenu.open(screen, menuX, aboveY(), false); + contextMenu.anchorY = Qt.binding(aboveY); + }); } function close() { - widgetIndex = -1; + if (!contextMenu.renderActive) { + widgetIndex = -1; + _anchor = null; + return; + } + contextMenu.hide(); } function toggleAction(id, enabled) { @@ -70,120 +83,25 @@ Item { SettingsData.set("controlCenterWidgets", widgets); } - MouseArea { - anchors.fill: parent - enabled: root.visible - acceptedButtons: Qt.AllButtons - onClicked: root.close() - onWheel: wheel => wheel.accepted = true - } + DankContextMenu { + id: contextMenu + layerNamespace: "dms:control-center-widget-options" + minMenuWidth: CcMetrics.configMenuWidth + customContentWidth: CcMetrics.configMenuWidth + keyboardNavigable: true + transientSurfaceTracker: root.transientSurfaceTracker - FocusScope { - id: focusScope - anchors.fill: parent - focus: root.visible - - Keys.onEscapePressed: event => { - root.close(); - event.accepted = true; - } - } - - Rectangle { - id: panel - - readonly property real preferredY: root.anchorY - height - Theme.spacingS < Theme.spacingS ? root.anchorY + root.anchorHeight + Theme.spacingS : root.anchorY - height - Theme.spacingS - - width: CcMetrics.configMenuWidth - height: Math.min(menu.implicitHeight, root.height - Theme.spacingS * 4) + Theme.spacingS * 2 - radius: Theme.windowRadius - color: CcMetrics.dialogColor - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - x: Math.max(Theme.spacingS, Math.min(root.anchorX + root.anchorWidth - width, root.width - width - Theme.spacingS)) - y: Math.max(Theme.spacingS, Math.min(preferredY, root.height - height - Theme.spacingS)) - opacity: root.visible ? 1 : 0 - scale: root.visible ? 1 : CcMetrics.popupEnterScale - transformOrigin: Item.TopRight - - Behavior on opacity { - enabled: CcMetrics.animationsEnabled - NumberAnimation { - duration: Theme.expressiveDurations.expressiveEffects - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.expressiveEffects - } + onOpenStateChanged: { + if (openState) + return; + root.widgetIndex = -1; + if (root._anchor?.visible && root._anchor.enabled) + root._anchor.forceActiveFocus(); + root._anchor = null; } - Behavior on scale { - enabled: CcMetrics.animationsEnabled - NumberAnimation { - duration: Theme.expressiveDurations.expressiveFastSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial - } - } - - // The popout is not a real subsurface, so fake compositor blur the same way the detail dialog does, but from the sheet content it covers. - Loader { - anchors.fill: parent - z: -1 - active: root.visible && root.backdrop !== null && CcMetrics.hideCoveredContent - - sourceComponent: ClippingRectangle { - id: clipArea - - // Children land in an internal content item, so `parent` never reaches this pad. - readonly property real pad: CcMetrics.backdropBlurRadius - - radius: panel.radius - color: "transparent" - - ShaderEffectSource { - id: backdropSource - - x: -clipArea.pad - y: -clipArea.pad - width: clipArea.width + clipArea.pad * 2 - height: clipArea.height + clipArea.pad * 2 - visible: false - sourceItem: root.backdrop - // mapFromItem is not reactive, so the capture is rebuilt from the panel's own geometry. - sourceRect: { - const origin = root.backdrop.mapFromItem(root, panel.x - clipArea.pad, panel.y - clipArea.pad); - return Qt.rect(origin.x, origin.y, width, height); - } - } - - MultiEffect { - anchors.fill: backdropSource - source: backdropSource - blurEnabled: true - blur: 1 - blurMax: CcMetrics.backdropBlurRadius - autoPaddingEnabled: false - } - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.AllButtons - onClicked: mouse => mouse.accepted = true - } - - DankFlickable { - anchors.fill: parent - anchors.margins: Theme.spacingS - clip: true - contentWidth: width - contentHeight: menu.implicitHeight - interactive: contentHeight > height - + customContent: Component { CcGroup { - id: menu - width: parent.width - CcListRow { visible: root.isPlugin iconName: "settings" @@ -239,12 +157,19 @@ Item { } CcToggleRow { - visible: root.isUser + visible: root.isUser || root.isQuickActions text: I18n.tr("Background") - checked: root.widgetData?.background !== false + checked: root.isUser ? root.widgetData?.background !== false : root.widgetData?.background === true onToggled: checked => root.persistOption("background", checked) } + CcToggleRow { + visible: root.isQuickActions && root.widgetData?.background === true + text: I18n.tr("Button backgrounds") + checked: root.widgetData?.buttonBackgrounds === true + onToggled: checked => root.persistOption("buttonBackgrounds", checked) + } + CcToggleRow { visible: root.isQuickActions text: I18n.tr("Highlight power", "toggle that gives the control center power button the error color") @@ -268,6 +193,7 @@ Item { compactMode: true dropdownWidth: parent.width + transientSurfaceTracker: contextMenu.transientSurfaceTracker currentValue: presets.find(p => p.minutes === (root.widgetData?.durationMinutes ?? 0))?.label ?? "" options: presets.map(p => p.label) onValueChanged: value => { diff --git a/quickshell/Modules/ControlCenter/ControlCenterContent.qml b/quickshell/Modules/ControlCenter/ControlCenterContent.qml index 37567b98c..0667b70ba 100644 --- a/quickshell/Modules/ControlCenter/ControlCenterContent.qml +++ b/quickshell/Modules/ControlCenter/ControlCenterContent.qml @@ -334,6 +334,7 @@ FocusScope { model: widgetModel screenName: root.host.triggerScreen?.name || "" screenModel: root.host.triggerScreen?.model || "" + transientSurfaceTracker: root.host.transientSurfaceTracker onCodecSelectorRequested: device => root.showCodecSelector(device) onPortSelectorRequested: node => root.showPortSelector(node) onBackRequested: root.goBack() @@ -376,11 +377,9 @@ FocusScope { Loader { id: configOverlayLoader - anchors.fill: parent - z: CcMetrics.overlayZ active: false sourceComponent: WidgetConfigOverlay { - backdrop: contentFlickable + transientSurfaceTracker: root.host.transientSurfaceTracker onVisibleChanged: { if (visible) return; diff --git a/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml b/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml index cda7dc7b4..2290146fa 100644 --- a/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml +++ b/quickshell/Modules/ControlCenter/Details/BluetoothDetail.qml @@ -16,7 +16,7 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - property Item menuParent: root + property var transientSurfaceTracker: null readonly property string title: I18n.tr("Bluetooth") readonly property var adapter: BluetoothService.adapter @@ -362,7 +362,7 @@ Item { CcMenu { id: deviceMenu - parent: root.menuParent + transientSurfaceTracker: root.transientSurfaceTracker } Connections { diff --git a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml index a0c3dca14..5f9aed0f0 100644 --- a/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml +++ b/quickshell/Modules/ControlCenter/Details/NetworkDetail.qml @@ -18,7 +18,7 @@ Item { LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.childrenInherit: true - property Item menuParent: root + property var transientSurfaceTracker: null readonly property string title: I18n.tr("Network") @@ -653,12 +653,12 @@ Item { CcMenu { id: wifiMenu - parent: root.menuParent + transientSurfaceTracker: root.transientSurfaceTracker } CcMenu { id: wiredMenu - parent: root.menuParent + transientSurfaceTracker: root.transientSurfaceTracker } Loader { diff --git a/quickshell/Modules/ControlCenter/Widgets/CcMenu.qml b/quickshell/Modules/ControlCenter/Widgets/CcMenu.qml index a38c27daf..b228d1783 100644 --- a/quickshell/Modules/ControlCenter/Widgets/CcMenu.qml +++ b/quickshell/Modules/ControlCenter/Widgets/CcMenu.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import QtQuick +import Quickshell import qs.Common import qs.Modules.ControlCenter import qs.Widgets @@ -9,221 +10,60 @@ Item { id: root property var items: [] - readonly property bool open: _open - property bool _open: false - property var _anchor: null - property int _focusIndex: -1 - + property var transientSurfaceTracker: null + property Item _anchor: null + readonly property bool open: menu.openState readonly property var visibleItems: (items || []).filter(item => item.visible !== false) signal closed - anchors.fill: parent - visible: _open || panel.opacity > 0 - z: CcMetrics.overlayZ - - function _measure() { - let widest = 0; - let hasIcon = false; - for (const item of visibleItems) { - labelMetrics.text = item.label || ""; - widest = Math.max(widest, labelMetrics.advanceWidth); - hasIcon = hasIcon || !!item.iconName; - } - const iconSpan = hasIcon ? Theme.iconSizeMedium + Theme.spacingM : 0; - panel.width = Math.max(CcMetrics.menuMinWidth, widest + iconSpan + Theme.spacingM * 2 + Theme.spacingS * 2); - } - function openAt(anchor) { + const window = root.QsWindow.window; + const screen = window?.screen; + if (!anchor || !screen) + return; + _anchor = anchor; - _focusIndex = -1; - _measure(); - _reposition(); - _open = true; - focusScope.forceActiveFocus(); + const pos = anchor.mapToGlobal(0, 0); + const x = pos.x - screen.x; + const y = pos.y - screen.y; + const menuX = I18n.isRtl ? x : x + anchor.width - menu.effectiveMenuWidth; + const below = y + anchor.height + Theme.spacingXS; + const menuY = below + menu.effectiveMenuHeight > screen.height - Theme.spacingS + ? y - menu.effectiveMenuHeight - Theme.spacingXS : below; + menu.open(screen, menuX, menuY, false); } function close() { - if (!_open) - return; - _open = false; - closed(); - if (_anchor) - _anchor.forceActiveFocus(); + menu.hide(); } - function _reposition() { - if (!_anchor) - return; - const pos = _anchor.mapToItem(root, 0, 0); - const maxX = root.width - panel.width - Theme.spacingS; - const preferredX = I18n.isRtl ? pos.x : pos.x + _anchor.width - panel.width; - panel.x = Math.max(Theme.spacingS, Math.min(preferredX, maxX)); - let y = pos.y + _anchor.height + Theme.spacingXS; - if (y + panel.height > root.height - Theme.spacingS) - y = pos.y - panel.height - Theme.spacingXS; - panel.y = Math.max(Theme.spacingS, y); - } + DankContextMenu { + id: menu + layerNamespace: "dms:control-center-menu" + minMenuWidth: CcMetrics.menuMinWidth + keyboardNavigable: true + transientSurfaceTracker: root.transientSurfaceTracker + menuItems: root.visibleItems.map(item => ({ + type: "item", + icon: item.iconName || "", + text: item.label || "", + enabled: item.enabled !== false, + isDestructive: item.destructive === true, + action: () => { + menu.hide(); + if (typeof item.action === "function") + item.action(); + } + })) - function _activate(index) { - const item = visibleItems[index]; - if (!item || item.enabled === false) - return; - close(); - if (typeof item.action === "function") - item.action(); - } - - function _moveFocus(delta) { - const count = visibleItems.length; - if (count === 0) - return; - let next = _focusIndex; - for (let i = 0; i < count; i++) { - next = (next + delta + count) % count; - if (visibleItems[next].enabled !== false) - break; - } - _focusIndex = next; - } - - MouseArea { - anchors.fill: parent - enabled: root._open - acceptedButtons: Qt.AllButtons - onClicked: root.close() - onWheel: wheel => wheel.accepted = true - } - - FocusScope { - id: focusScope - anchors.fill: parent - focus: root._open - - Keys.onPressed: event => { - switch (event.key) { - case Qt.Key_Escape: - root.close(); - break; - case Qt.Key_Down: - root._moveFocus(1); - break; - case Qt.Key_Up: - root._moveFocus(-1); - break; - case Qt.Key_Return: - case Qt.Key_Enter: - case Qt.Key_Space: - root._activate(root._focusIndex); - break; - default: + onOpenStateChanged: { + if (openState) return; - } - event.accepted = true; - } - } - - StyledTextMetrics { - id: labelMetrics - font.pixelSize: Theme.fontSizeMedium - } - - Rectangle { - id: panel - width: CcMetrics.menuMinWidth - height: root.visibleItems.length * Theme.menuItemHeight + Theme.spacingS * 2 - radius: Theme.windowRadius - color: Theme.foregroundColor(Theme.chipSurface, Theme.isFloatingWindow(root)) - border.width: Theme.layerOutlineWidth - border.color: Theme.outlineMedium - opacity: root._open ? 1 : 0 - scale: root._open ? 1 : CcMetrics.popupEnterScale - transformOrigin: I18n.isRtl ? Item.TopLeft : Item.TopRight - - Behavior on opacity { - enabled: CcMetrics.animationsEnabled - NumberAnimation { - duration: Theme.expressiveDurations.expressiveEffects - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.expressiveEffects - } - } - - Behavior on scale { - enabled: CcMetrics.animationsEnabled - NumberAnimation { - duration: Theme.expressiveDurations.expressiveFastSpatial - easing.type: Easing.BezierSpline - easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.AllButtons - onClicked: mouse => mouse.accepted = true - onWheel: wheel => wheel.accepted = true - } - - Column { - anchors.left: parent.left - anchors.top: parent.top - anchors.margins: Theme.spacingS - width: parent.width - Theme.spacingS * 2 - - Repeater { - model: root.visibleItems - - Rectangle { - id: row - - required property var modelData - required property int index - - readonly property bool itemEnabled: modelData.enabled !== false - readonly property color contentColor: { - if (!itemEnabled) - return Theme.onSurface_38; - return modelData.destructive ? Theme.error : Theme.surfaceText; - } - - width: parent.width - height: Theme.menuItemHeight - radius: Theme.cornerRadiusS - color: root._focusIndex === index && !rowLayer.containsMouse ? Theme.withAlpha(contentColor, Theme.stateLayerFocus) : "transparent" - - Row { - id: rowContent - anchors.left: parent.left - anchors.leftMargin: Theme.spacingM - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacingM - - DankIcon { - name: row.modelData.iconName || "" - size: Theme.iconSizeMedium - color: row.contentColor - visible: name !== "" - anchors.verticalCenter: parent.verticalCenter - } - - StyledText { - text: row.modelData.label || "" - font.pixelSize: Theme.fontSizeMedium - color: row.contentColor - anchors.verticalCenter: parent.verticalCenter - } - } - - StateLayer { - id: rowLayer - disabled: !row.itemEnabled - stateColor: row.contentColor - cornerRadius: row.radius - onClicked: root._activate(row.index) - } - } - } + root.closed(); + if (root._anchor?.visible && root._anchor.enabled) + root._anchor.forceActiveFocus(); + root._anchor = null; } } } diff --git a/quickshell/Modules/ControlCenter/Widgets/CcSheetDialog.qml b/quickshell/Modules/ControlCenter/Widgets/CcSheetDialog.qml index 805430f89..a56e58da9 100644 --- a/quickshell/Modules/ControlCenter/Widgets/CcSheetDialog.qml +++ b/quickshell/Modules/ControlCenter/Widgets/CcSheetDialog.qml @@ -15,6 +15,7 @@ Item { property bool shown: false property real panelWidth: CcMetrics.dialogWidth property vector4d cornerRadii: Qt.vector4d(Theme.windowRadius, Theme.windowRadius, Theme.windowRadius, Theme.windowRadius) + property Item backdrop: null readonly property real presence: panel.opacity default property alias content: contentSlot.data @@ -112,6 +113,17 @@ Item { } } + // In-window sheets get no compositor blur, so blur the covered content behind the panel only. + Loader { + anchors.fill: parent + z: -1 + active: root.backdrop !== null && root.visible && CcMetrics.hideCoveredContent + sourceComponent: BackdropBlur { + radius: panel.radius + sourceItem: root.backdrop + } + } + MouseArea { anchors.fill: parent hoverEnabled: true diff --git a/quickshell/Modules/ControlCenter/Widgets/QuickActions.qml b/quickshell/Modules/ControlCenter/Widgets/QuickActions.qml index f267f03e8..f03041395 100644 --- a/quickshell/Modules/ControlCenter/Widgets/QuickActions.qml +++ b/quickshell/Modules/ControlCenter/Widgets/QuickActions.qml @@ -21,7 +21,9 @@ Item { readonly property Item passthrough: actionGrid readonly property bool editMode: host?.editMode ?? false readonly property var actions: WidgetUtils.enabledQuickActions(widgetData) - readonly property real bodyRadius: Theme.cornerRadiusL + readonly property bool background: widgetData?.background === true + readonly property bool buttonBackgrounds: widgetData?.buttonBackgrounds === true + readonly property real bodyRadius: background ? Theme.cornerRadiusXL : Theme.cornerRadiusL readonly property int actionColumns: Math.max(1, Math.min(actions.length, Math.floor((width + CcMetrics.actionGap) / (CcMetrics.actionSize + CcMetrics.actionGap)))) width: parent?.width ?? 0 @@ -46,6 +48,15 @@ Item { } } + Rectangle { + anchors.fill: parent + radius: root.bodyRadius + color: Theme.foregroundColor(Theme.cardSurface) + border.width: Theme.layerOutlineWidth + border.color: Theme.outlineMedium + visible: root.background + } + Grid { id: actionGrid @@ -64,10 +75,10 @@ Item { buttonSize: CcMetrics.actionSize iconSize: CcMetrics.actionIconSize - circular: false + circular: root.background iconName: WidgetUtils.quickActionIcon(actionId) iconColor: editing ? Theme.onPrimary : destructive ? Theme.error : Theme.onSurface - backgroundColor: editing ? Theme.primary : Theme.foregroundColor(Theme.cardSurface) + backgroundColor: editing ? Theme.primary : root.background ? (root.buttonBackgrounds ? Theme.foregroundColor(Theme.chipSurface) : "transparent") : Theme.foregroundColor(Theme.cardSurface) tooltipText: I18n.tr(WidgetUtils.quickActionLabel(actionId)) checkable: actionId === "edit" checked: editing diff --git a/quickshell/Modules/DankDash/DankDashPopout.qml b/quickshell/Modules/DankDash/DankDashPopout.qml index 4be3d1662..2e17d3859 100644 --- a/quickshell/Modules/DankDash/DankDashPopout.qml +++ b/quickshell/Modules/DankDash/DankDashPopout.qml @@ -562,6 +562,7 @@ DankPopout { DashOptionsSheet { id: tabOptions + backdrop: contentColumn onDismissed: mainContainer.focusInitial() } @@ -656,7 +657,7 @@ DankPopout { height: Math.min(parent.height, implicitHeight) visible: root.editMode vertical: root.verticalNavigation - overlayParent: mainContainer + transientSurfaceTracker: root.transientSurfaceTracker entryId: root.activeTabId tabItem: pages.currentItem editMode: root.editMode diff --git a/quickshell/Modules/DankDash/DashPageActions.qml b/quickshell/Modules/DankDash/DashPageActions.qml index 3cec46204..5556a5a6a 100644 --- a/quickshell/Modules/DankDash/DashPageActions.qml +++ b/quickshell/Modules/DankDash/DashPageActions.qml @@ -11,7 +11,7 @@ Item { id: root required property string entryId - required property Item overlayParent + property var transientSurfaceTracker: null property var tabItem: null property bool vertical: false property bool editMode: false @@ -56,7 +56,7 @@ Item { CcMenu { id: customMenu - parent: root.overlayParent + transientSurfaceTracker: root.transientSurfaceTracker items: root.tabItem?.menuActions ?? [] } diff --git a/quickshell/Modules/DankDash/DashWidgetGrid.qml b/quickshell/Modules/DankDash/DashWidgetGrid.qml index 6585b22d9..55444fc87 100644 --- a/quickshell/Modules/DankDash/DashWidgetGrid.qml +++ b/quickshell/Modules/DankDash/DashWidgetGrid.qml @@ -18,6 +18,7 @@ Item { property bool live: Window.window?.visible ?? false property int columns: width < Theme.smallBreakpoint ? 2 : 4 property real cardRadius: Theme.cornerRadiusXL + property var transientSurfaceTracker: null readonly property var widgets: WidgetUtils.resolve(definitions, DashRegistry.widgetLayout(entryId)) readonly property var addable: definitions.filter(d => !widgets.some(w => w.id === d.id)) readonly property Item focusTarget: grid @@ -200,7 +201,7 @@ Item { CcMenu { id: addMenu - parent: root.Window.window?.contentItem ?? root + transientSurfaceTracker: root.transientSurfaceTracker items: root.addable.map(spec => ({ label: spec.text, iconName: spec.icon, @@ -214,7 +215,7 @@ Item { CcMenu { id: widgetMenu - parent: root.Window.window?.contentItem ?? root + transientSurfaceTracker: root.transientSurfaceTracker property string widgetId: "" readonly property int widgetIndex: root.widgets.findIndex(w => w.id === widgetId) readonly property var widget: root.widgets[widgetIndex] ?? {} diff --git a/quickshell/Modules/DankDash/OverviewTab.qml b/quickshell/Modules/DankDash/OverviewTab.qml index d6e5185d2..5aee54a0b 100644 --- a/quickshell/Modules/DankDash/OverviewTab.qml +++ b/quickshell/Modules/DankDash/OverviewTab.qml @@ -111,6 +111,7 @@ FocusScope { CcMenu { id: addMenu + transientSurfaceTracker: root.transientSurfaceTracker items: root.addable.map(candidate => ({ "label": candidate.entry.card.text, @@ -121,11 +122,13 @@ FocusScope { DashOptionsSheet { id: optionsSheet + backdrop: grid onDismissed: root.navFocusRequested(false) } CcSheetDialog { id: detailSheet + backdrop: grid property var eventData: null @@ -168,6 +171,7 @@ FocusScope { CcSheetDialog { id: editorSheet + backdrop: grid property var eventData: null property date initialDate: new Date() diff --git a/quickshell/Modules/DankDash/WeatherTab.qml b/quickshell/Modules/DankDash/WeatherTab.qml index 219328e3e..8be2d3cd2 100644 --- a/quickshell/Modules/DankDash/WeatherTab.qml +++ b/quickshell/Modules/DankDash/WeatherTab.qml @@ -15,6 +15,7 @@ Item { property bool live: Window.window?.visible ?? false property bool editMode: false property bool weatherRefHeld: false + property var transientSurfaceTracker: null readonly property var options: DashRegistry.resolvedOptions(entryId) readonly property bool available: WeatherService.weather.available readonly property var addable: widgets.addable @@ -69,6 +70,7 @@ Item { id: widgets anchors.fill: parent entryId: root.entryId + transientSurfaceTracker: root.transientSurfaceTracker live: root.live && root.available editMode: root.editMode visible: root.available || root.editMode diff --git a/quickshell/Modules/DankIsland/Activities/ControlCenterExpanded.qml b/quickshell/Modules/DankIsland/Activities/ControlCenterExpanded.qml index 66f595fa5..8be4d5ad5 100644 --- a/quickshell/Modules/DankIsland/Activities/ControlCenterExpanded.qml +++ b/quickshell/Modules/DankIsland/Activities/ControlCenterExpanded.qml @@ -70,6 +70,7 @@ FocusScope { readonly property var triggerScreen: root.effectiveScreen readonly property var colorPickerModal: PopoutService.colorPickerModal readonly property var powerMenuModalLoader: PopoutService.powerMenuModalLoader + readonly property var transientSurfaceTracker: root.controller.transientSurfaces readonly property real alignedX: root.alignedX readonly property real alignedY: root.alignedY readonly property real alignedWidth: root.alignedWidth diff --git a/quickshell/Modules/DankIsland/Activities/DashTabFace.qml b/quickshell/Modules/DankIsland/Activities/DashTabFace.qml index 3998e73ca..6cef41fb9 100644 --- a/quickshell/Modules/DankIsland/Activities/DashTabFace.qml +++ b/quickshell/Modules/DankIsland/Activities/DashTabFace.qml @@ -174,7 +174,7 @@ FocusScope { anchors.verticalCenter: parent.verticalCenter width: Math.min(parent.width, implicitWidth) height: parent.height - overlayParent: root + transientSurfaceTracker: root.controller.transientSurfaces entryId: root.entryId tabItem: root.tab editMode: root.editMode @@ -256,6 +256,7 @@ FocusScope { DashOptionsSheet { id: tabOptions + backdrop: pages onDismissed: root.focusFace() } diff --git a/quickshell/Modules/DankIsland/Activities/WeatherExpanded.qml b/quickshell/Modules/DankIsland/Activities/WeatherExpanded.qml index 253c0cf41..7ae795434 100644 --- a/quickshell/Modules/DankIsland/Activities/WeatherExpanded.qml +++ b/quickshell/Modules/DankIsland/Activities/WeatherExpanded.qml @@ -11,6 +11,7 @@ DashTabFace { WeatherTab { live: root.live editMode: root.editMode + transientSurfaceTracker: root.controller.transientSurfaces } } diff --git a/quickshell/Modules/Plugins/DashTabComponent.qml b/quickshell/Modules/Plugins/DashTabComponent.qml index b1f7313aa..349aa51ab 100644 --- a/quickshell/Modules/Plugins/DashTabComponent.qml +++ b/quickshell/Modules/Plugins/DashTabComponent.qml @@ -9,6 +9,7 @@ Item { property var pluginService: null property var popoutService: null property var dashHost: null + property var transientSurfaceTracker: null property string entryId: "" property Item focusTarget: null property bool editMode: false @@ -31,6 +32,13 @@ Item { onPluginServiceChanged: loadPluginData() onPluginIdChanged: loadPluginData() + Binding { + target: root.widgetGrid + property: "transientSurfaceTracker" + value: root.transientSurfaceTracker + when: root.widgetGrid !== null + } + Connections { target: root.pluginService enabled: root.pluginService !== null diff --git a/quickshell/Widgets/BackdropBlur.qml b/quickshell/Widgets/BackdropBlur.qml index 56c0c0dcf..3313cd40c 100644 --- a/quickshell/Widgets/BackdropBlur.qml +++ b/quickshell/Widgets/BackdropBlur.qml @@ -36,6 +36,7 @@ Item { visible: root.blurActive blurEnabled: root.blurActive blurMax: root.blurMax + autoPaddingEnabled: false blur: root.blurAmount maskEnabled: true maskSource: maskRect diff --git a/quickshell/Widgets/DankContextMenu.qml b/quickshell/Widgets/DankContextMenu.qml index af291def5..88ccceb41 100644 --- a/quickshell/Widgets/DankContextMenu.qml +++ b/quickshell/Widgets/DankContextMenu.qml @@ -15,6 +15,8 @@ Item { height: 0 property var menuItems: [] + property Component customContent: null + property real customContentWidth: 0 property string layerNamespace: "dms:context-menu" property real menuMargin: Theme.spacingS property real minMenuWidth: Theme.fieldDefaultWidth @@ -41,9 +43,9 @@ Item { } return longest; } - readonly property real naturalMenuWidth: Math.max(minMenuWidth, menuTextMetrics.width + Theme.iconSize + Theme.spacingS * 5) + readonly property real naturalMenuWidth: customContent ? Math.max(minMenuWidth, customContentWidth) : Math.max(minMenuWidth, menuTextMetrics.width + Theme.iconSize + Theme.spacingS * 5) readonly property real effectiveMenuWidth: Math.max(0, Math.min(maxMenuWidth, naturalMenuWidth)) - readonly property real naturalMenuHeight: menuItemsHeight() + Theme.spacingS * 2 + readonly property real naturalMenuHeight: (customContent ? customContentLoader.implicitHeight : menuItemsHeight()) + Theme.spacingS * 2 readonly property real effectiveMenuHeight: Math.min(maxMenuHeight, naturalMenuHeight) readonly property bool menuScrolls: naturalMenuHeight > effectiveMenuHeight + 0.5 readonly property int visibleItemCount: menuItems.filter(menuItem => menuItem.type === "item").length @@ -113,6 +115,8 @@ Item { } function activate(menuItem) { + if (menuItem?.enabled === false) + return; if (typeof menuItem?.action === "function") menuItem.action(); hide(); @@ -122,7 +126,11 @@ Item { if (visibleItemCount === 0) return; keyboardNavigation = true; - selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount; + for (let step = 0; step < visibleItemCount; step++) { + selectedMenuIndex = (selectedMenuIndex + 1) % visibleItemCount; + if (menuItems[selectedDelegateIndex()]?.enabled !== false) + break; + } ensureSelectedVisible(); } @@ -130,7 +138,11 @@ Item { if (visibleItemCount === 0) return; keyboardNavigation = true; - selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount; + for (let step = 0; step < visibleItemCount; step++) { + selectedMenuIndex = (selectedMenuIndex - 1 + visibleItemCount) % visibleItemCount; + if (menuItems[selectedDelegateIndex()]?.enabled !== false) + break; + } ensureSelectedVisible(); } @@ -295,9 +307,17 @@ Item { width: menuFlickable.width spacing: Theme.groupedListGap + Loader { + id: customContentLoader + width: menuColumn.width + active: root.customContent !== null + visible: active + sourceComponent: root.customContent + } + Repeater { id: menuRepeater - model: root.menuItems + model: root.customContent ? [] : root.menuItems Item { id: menuItemDelegate @@ -322,7 +342,10 @@ Item { id: menuRow readonly property bool selected: root.keyboardNavigation && root.selectedMenuIndex === menuItemDelegate.itemIndex readonly property bool destructive: menuItemDelegate.modelData?.isDestructive ?? false + readonly property bool itemEnabled: menuItemDelegate.modelData?.enabled !== false readonly property color contentColor: { + if (!itemEnabled) + return Theme.onSurface_38; if (destructive) return Theme.error; return selected ? Theme.onSelectedContainer : Theme.onSurface; @@ -331,6 +354,8 @@ Item { anchors.fill: parent radius: Theme.cornerRadiusS color: { + if (!itemEnabled) + return "transparent"; if (destructive) return selected ? Theme.errorSelected : itemMouseArea.containsMouse ? Theme.errorHover : "transparent"; return selected ? Theme.selectedContainer : itemMouseArea.pressed ? Theme.withAlpha(Theme.onSurface, Theme.stateLayerPressed) : itemMouseArea.containsMouse ? Theme.withAlpha(Theme.onSurface, Theme.stateLayerHover) : "transparent"; @@ -371,6 +396,7 @@ Item { MouseArea { id: itemMouseArea anchors.fill: parent + enabled: menuRow.itemEnabled hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: {