From f15d49d80a3d6f89d1a8ec793b641c883a55f034 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 30 Mar 2026 11:52:35 -0400 Subject: [PATCH] blur: add blur support with ext-bg-effect --- quickshell/Common/SettingsData.qml | 8 ++ quickshell/Common/settings/SettingsSpec.js | 4 + quickshell/Modals/Common/DankModal.qml | 36 +++++- .../DankLauncherV2/DankLauncherV2Modal.qml | 78 +++++++++---- quickshell/Modules/DankBar/CenterSection.qml | 2 + quickshell/Modules/DankBar/DankBarContent.qml | 38 ++++++ quickshell/Modules/DankBar/DankBarWindow.qml | 109 +++++++++++++++++- quickshell/Modules/DankBar/LeftSection.qml | 3 + quickshell/Modules/DankBar/RightSection.qml | 3 + quickshell/Modules/DankBar/WidgetHost.qml | 9 ++ .../Modules/DankBar/Widgets/AppsDock.qml | 2 +- .../DankBar/Widgets/ClipboardButton.qml | 18 ++- quickshell/Modules/DankBar/Widgets/Media.qml | 4 +- .../Modules/DankBar/Widgets/NotepadButton.qml | 4 +- .../Modules/DankBar/Widgets/RunningApps.qml | 20 +++- .../Modules/DankBar/Widgets/SystemTrayBar.qml | 57 +++++++-- .../DankBar/Widgets/WorkspaceSwitcher.qml | 23 ++++ quickshell/Modules/Dock/Dock.qml | 18 +++ quickshell/Modules/Dock/DockContextMenu.qml | 13 ++- .../Notifications/Popup/NotificationPopup.qml | 20 +++- quickshell/Modules/Plugins/BasePill.qml | 25 +++- .../Modules/Plugins/PluginComponent.qml | 3 + .../ProcessList/ProcessContextMenu.qml | 5 +- .../Modules/Settings/ThemeColorsTab.qml | 81 ++++++++++++- quickshell/Services/BlurService.qml | 88 ++++++++++++++ quickshell/Widgets/DankOSD.qml | 14 ++- quickshell/Widgets/DankPopout.qml | 15 ++- quickshell/Widgets/WindowBlur.qml | 65 +++++++++++ 28 files changed, 705 insertions(+), 60 deletions(-) create mode 100644 quickshell/Services/BlurService.qml create mode 100644 quickshell/Widgets/WindowBlur.qml diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 88e17fa1..d1c48860 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -186,6 +186,14 @@ Singleton { onPopoutElevationEnabledChanged: saveSettings() property bool barElevationEnabled: true onBarElevationEnabledChanged: saveSettings() + property bool blurEnabled: false + onBlurEnabledChanged: saveSettings() + property string blurBorderColor: "outline" + onBlurBorderColorChanged: saveSettings() + property string blurBorderCustomColor: "#ffffff" + onBlurBorderCustomColorChanged: saveSettings() + property real blurBorderOpacity: 1.0 + onBlurBorderOpacityChanged: saveSettings() property string wallpaperFillMode: "Fill" property bool blurredWallpaperLayer: false property bool blurWallpaperOnOverview: false diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index bea3782e..803124c5 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -58,6 +58,10 @@ var SPEC = { modalElevationEnabled: { def: true }, popoutElevationEnabled: { def: true }, barElevationEnabled: { def: true }, + blurEnabled: { def: false }, + blurBorderColor: { def: "outline" }, + blurBorderCustomColor: { def: "#ffffff" }, + blurBorderOpacity: { def: 1.0, coerce: percentToUnit }, wallpaperFillMode: { def: "Fill" }, blurredWallpaperLayer: { def: false }, blurWallpaperOnOverview: { def: false }, diff --git a/quickshell/Modals/Common/DankModal.qml b/quickshell/Modals/Common/DankModal.qml index f955ac64..4aa378e6 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -3,6 +3,7 @@ import Quickshell import Quickshell.Wayland import qs.Common import qs.Services +import qs.Widgets Item { id: root @@ -59,11 +60,25 @@ Item { function open() { closeTimer.stop(); const focusedScreen = CompositorService.getFocusedScreen(); + const screenChanged = focusedScreen && contentWindow.screen !== focusedScreen; if (focusedScreen) { + if (screenChanged) + contentWindow.visible = false; contentWindow.screen = focusedScreen; - if (!useSingleWindow) + if (!useSingleWindow) { + if (screenChanged) + clickCatcher.visible = false; clickCatcher.screen = focusedScreen; + } } + if (screenChanged) { + Qt.callLater(() => root._finishOpen()); + } else { + _finishOpen(); + } + } + + function _finishOpen() { ModalManager.openModal(root); shouldBeVisible = true; if (!useSingleWindow) @@ -215,6 +230,16 @@ Item { visible: false color: "transparent" + WindowBlur { + targetWindow: contentWindow + readonly property real s: Math.min(1, modalContainer.scaleValue) + blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr) + blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr) + blurWidth: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0 + blurHeight: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0 + blurRadius: root.cornerRadius + } + WlrLayershell.namespace: root.layerNamespace WlrLayershell.layer: { if (root.useOverlayLayer) @@ -393,6 +418,15 @@ Item { shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" } + Rectangle { + anchors.fill: parent + radius: root.cornerRadius + color: "transparent" + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + z: 100 + } + FocusScope { anchors.fill: parent focus: root.shouldBeVisible diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml index 1e886d00..859022cc 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml @@ -4,6 +4,7 @@ import Quickshell.Wayland import Quickshell.Hyprland import qs.Common import qs.Services +import qs.Widgets Item { id: root @@ -134,40 +135,47 @@ Item { } } - function show() { - closeCleanupTimer.stop(); + function _finishShow(query, mode) { + spotlightOpen = true; isClosing = false; openedFromOverview = false; - var focusedScreen = CompositorService.getFocusedScreen(); - if (focusedScreen) - launcherWindow.screen = focusedScreen; - - spotlightOpen = true; keyboardActive = true; ModalManager.openModal(root); if (useHyprlandFocusGrab) focusGrab.active = true; - _ensureContentLoadedAndInitialize("", ""); + _ensureContentLoadedAndInitialize(query || "", mode || ""); + } + + function show() { + closeCleanupTimer.stop(); + + var focusedScreen = CompositorService.getFocusedScreen(); + if (focusedScreen && launcherWindow.screen !== focusedScreen) { + spotlightOpen = false; + isClosing = false; + launcherWindow.screen = focusedScreen; + Qt.callLater(() => root._finishShow("", "")); + return; + } + + _finishShow("", ""); } function showWithQuery(query) { closeCleanupTimer.stop(); - isClosing = false; - openedFromOverview = false; var focusedScreen = CompositorService.getFocusedScreen(); - if (focusedScreen) + if (focusedScreen && launcherWindow.screen !== focusedScreen) { + spotlightOpen = false; + isClosing = false; launcherWindow.screen = focusedScreen; + Qt.callLater(() => root._finishShow(query, "")); + return; + } - spotlightOpen = true; - keyboardActive = true; - ModalManager.openModal(root); - if (useHyprlandFocusGrab) - focusGrab.active = true; - - _ensureContentLoadedAndInitialize(query, ""); + _finishShow(query, ""); } function hide() { @@ -191,14 +199,20 @@ Item { function showWithMode(mode) { closeCleanupTimer.stop(); + + var focusedScreen = CompositorService.getFocusedScreen(); + if (focusedScreen && launcherWindow.screen !== focusedScreen) { + spotlightOpen = false; + isClosing = false; + launcherWindow.screen = focusedScreen; + Qt.callLater(() => root._finishShow("", mode)); + return; + } + + spotlightOpen = true; isClosing = false; openedFromOverview = false; - var focusedScreen = CompositorService.getFocusedScreen(); - if (focusedScreen) - launcherWindow.screen = focusedScreen; - - spotlightOpen = true; keyboardActive = true; ModalManager.openModal(root); if (useHyprlandFocusGrab) @@ -295,6 +309,16 @@ Item { color: "transparent" exclusionMode: ExclusionMode.Ignore + WindowBlur { + targetWindow: launcherWindow + readonly property real s: Math.min(1, modalContainer.scale) + blurX: root.modalX + root.modalWidth * (1 - s) * 0.5 + blurY: root.modalY + root.modalHeight * (1 - s) * 0.5 + blurWidth: (contentVisible && modalContainer.opacity > 0) ? root.modalWidth * s : 0 + blurHeight: (contentVisible && modalContainer.opacity > 0) ? root.modalHeight * s : 0 + blurRadius: root.cornerRadius + } + WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: { switch (Quickshell.env("DMS_MODAL_LAYER")) { @@ -428,6 +452,14 @@ Item { event.accepted = true; } } + + Rectangle { + anchors.fill: parent + radius: root.cornerRadius + color: "transparent" + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + } } } } diff --git a/quickshell/Modules/DankBar/CenterSection.qml b/quickshell/Modules/DankBar/CenterSection.qml index 8c9692ed..df86d4d3 100644 --- a/quickshell/Modules/DankBar/CenterSection.qml +++ b/quickshell/Modules/DankBar/CenterSection.qml @@ -14,6 +14,7 @@ Item { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -357,6 +358,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow isFirst: index === 0 isLast: index === centerRepeater.count - 1 sectionSpacing: parent.itemSpacing diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index fb0db4d9..a826e7bd 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -14,6 +14,8 @@ Item { required property var rootWindow required property var barConfig + readonly property var blurBarWindow: barWindow + property var leftWidgetsModel property var centerWidgetsModel property var rightWidgetsModel @@ -408,6 +410,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: hLeftSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } RightSection { id: hRightSection @@ -434,6 +442,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: hRightSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } CenterSection { id: hCenterSection @@ -460,6 +474,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: hCenterSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } } Item { @@ -493,6 +513,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: vLeftSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } CenterSection { id: vCenterSection @@ -520,6 +546,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: vCenterSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } RightSection { id: vRightSection @@ -548,6 +580,12 @@ Item { value: topBarContent.barConfig restoreMode: Binding.RestoreNone } + Binding { + target: vRightSection + property: "blurBarWindow" + value: topBarContent.blurBarWindow + restoreMode: Binding.RestoreNone + } } } diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index f95e5b68..d8b0216c 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -97,6 +97,112 @@ PanelWindow { } } + property var blurRegion: null + property var _blurWidgetItems: [] + + function registerBlurWidget(item) { + if (_blurWidgetItems.indexOf(item) >= 0) + return; + _blurWidgetItems = _blurWidgetItems.concat([item]); + _blurRebuildTimer.restart(); + } + + function unregisterBlurWidget(item) { + const idx = _blurWidgetItems.indexOf(item); + if (idx < 0) + return; + const arr = _blurWidgetItems.slice(); + arr.splice(idx, 1); + _blurWidgetItems = arr; + _blurRebuildTimer.restart(); + } + + Timer { + id: _blurRebuildTimer + interval: 1 + onTriggered: barBlur.rebuild() + } + + Item { + id: barBlur + visible: false + + readonly property bool barHasTransparency: barWindow._backgroundAlpha > 0 && barWindow._backgroundAlpha < 1 + + function rebuild() { + teardown(); + if (!BlurService.enabled || !BlurService.available) + return; + + const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0); + const hasBar = barHasTransparency; + if (!hasBar && widgets.length === 0) + return; + + const cr = Theme.cornerRadius; + let qml = 'import QtQuick; import Quickshell; Region {'; + for (let i = 0; i < widgets.length; i++) { + qml += ` property Item w${i}; Region { item: w${i}; radius: ${cr} }`; + } + qml += '}'; + + try { + const region = Qt.createQmlObject(qml, barWindow, "BarBlurRegion"); + + if (hasBar) { + region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x); + region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y); + region.width = Qt.binding(() => barUnitInset.width); + region.height = Qt.binding(() => barUnitInset.height); + region.radius = Qt.binding(() => barBackground.rt); + } + + for (let i = 0; i < widgets.length; i++) { + region[`w${i}`] = widgets[i]; + } + + barWindow.BackgroundEffect.blurRegion = region; + barWindow.blurRegion = region; + } catch (e) { + console.warn("BarBlur: Failed to create blur region:", e); + } + } + + function teardown() { + if (!barWindow.blurRegion) + return; + try { + barWindow.BackgroundEffect.blurRegion = null; + } catch (e) {} + barWindow.blurRegion.destroy(); + barWindow.blurRegion = null; + } + + onBarHasTransparencyChanged: _blurRebuildTimer.restart() + + Connections { + target: BlurService + function onEnabledChanged() { + barBlur.rebuild(); + } + } + + Connections { + target: topBarSlide + function onXChanged() { + if (barWindow.blurRegion) + barWindow.blurRegion.changed(); + } + function onYChanged() { + if (barWindow.blurRegion) + barWindow.blurRegion.changed(); + } + } + + Component.onCompleted: rebuild() + Component.onDestruction: teardown() + } + WlrLayershell.layer: dBarLayer WlrLayershell.namespace: "dms:bar" @@ -711,7 +817,8 @@ PanelWindow { onHasActivePopoutChanged: evaluateReveal() function updateActivePopoutState() { - if (!barWindow.screen) return; + if (!barWindow.screen) + return; const screenName = barWindow.screen.name; const activePopout = PopoutManager.currentPopoutsByScreen[screenName]; const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName]; diff --git a/quickshell/Modules/DankBar/LeftSection.qml b/quickshell/Modules/DankBar/LeftSection.qml index 6fd7591f..d7219db2 100644 --- a/quickshell/Modules/DankBar/LeftSection.qml +++ b/quickshell/Modules/DankBar/LeftSection.qml @@ -13,6 +13,7 @@ Item { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -59,6 +60,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow isFirst: index === 0 isLast: index === rowRepeater.count - 1 sectionSpacing: parent.rowSpacing @@ -103,6 +105,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow isFirst: index === 0 isLast: index === columnRepeater.count - 1 sectionSpacing: parent.columnSpacing diff --git a/quickshell/Modules/DankBar/RightSection.qml b/quickshell/Modules/DankBar/RightSection.qml index 98fe03f9..58844bc0 100644 --- a/quickshell/Modules/DankBar/RightSection.qml +++ b/quickshell/Modules/DankBar/RightSection.qml @@ -13,6 +13,7 @@ Item { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property bool overrideAxisLayout: false property bool forceVerticalLayout: false @@ -61,6 +62,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow isFirst: index === 0 isLast: index === rowRepeater.count - 1 sectionSpacing: parent.rowSpacing @@ -105,6 +107,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow isFirst: index === 0 isLast: index === columnRepeater.count - 1 sectionSpacing: parent.columnSpacing diff --git a/quickshell/Modules/DankBar/WidgetHost.qml b/quickshell/Modules/DankBar/WidgetHost.qml index 74825e24..01d1c735 100644 --- a/quickshell/Modules/DankBar/WidgetHost.qml +++ b/quickshell/Modules/DankBar/WidgetHost.qml @@ -16,6 +16,7 @@ Loader { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property bool isFirst: false property bool isLast: false property real sectionSpacing: 0 @@ -92,6 +93,14 @@ Loader { restoreMode: Binding.RestoreNone } + Binding { + target: root.item + when: root.item && "blurBarWindow" in root.item + property: "blurBarWindow" + value: root.blurBarWindow + restoreMode: Binding.RestoreNone + } + Binding { target: root.item when: root.item && "axis" in root.item diff --git a/quickshell/Modules/DankBar/Widgets/AppsDock.qml b/quickshell/Modules/DankBar/Widgets/AppsDock.qml index f5b3a760..294102dd 100644 --- a/quickshell/Modules/DankBar/Widgets/AppsDock.qml +++ b/quickshell/Modules/DankBar/Widgets/AppsDock.qml @@ -630,7 +630,7 @@ BasePill { if (appItem.isFocused && colorizeEnabled) { return mouseArea.containsMouse ? Theme.withAlpha(Qt.lighter(appItem.activeOverlayColor, 1.3), 0.4) : Theme.withAlpha(appItem.activeOverlayColor, 0.3); } - return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"; + return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"; } border.width: dragHandler.dragging ? 2 : 0 diff --git a/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml b/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml index d940cfe0..e970662f 100644 --- a/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml +++ b/quickshell/Modules/DankBar/Widgets/ClipboardButton.qml @@ -3,6 +3,7 @@ import Quickshell import Quickshell.Wayland import qs.Common import qs.Modules.Plugins +import qs.Services import qs.Widgets BasePill { @@ -93,6 +94,15 @@ BasePill { PanelWindow { id: contextMenuWindow + WindowBlur { + targetWindow: contextMenuWindow + blurX: menuContainer.x + blurY: menuContainer.y + blurWidth: contextMenuWindow.visible ? menuContainer.width : 0 + blurHeight: contextMenuWindow.visible ? menuContainer.height : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: "dms:clipboard-context-menu" property bool isVertical: false @@ -187,8 +197,8 @@ BasePill { height: Math.max(64, menuColumn.implicitHeight + Theme.spacingS * 2) 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: 1 + border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: BlurService.enabled ? BlurService.borderWidth : 1 opacity: contextMenuWindow.visible ? 1 : 0 visible: opacity > 0 @@ -224,7 +234,7 @@ BasePill { width: parent.width height: 30 radius: Theme.cornerRadius - color: clearAllArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: clearAllArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" Row { anchors.fill: parent @@ -264,7 +274,7 @@ BasePill { width: parent.width height: 30 radius: Theme.cornerRadius - color: savedItemsArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: savedItemsArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" Row { anchors.fill: parent diff --git a/quickshell/Modules/DankBar/Widgets/Media.qml b/quickshell/Modules/DankBar/Widgets/Media.qml index c7d6aac9..ae1f2ad7 100644 --- a/quickshell/Modules/DankBar/Widgets/Media.qml +++ b/quickshell/Modules/DankBar/Widgets/Media.qml @@ -354,7 +354,7 @@ BasePill { height: 20 radius: 10 anchors.verticalCenter: parent.verticalCenter - color: prevArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: prevArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" visible: root.playerAvailable opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3 @@ -411,7 +411,7 @@ BasePill { height: 20 radius: 10 anchors.verticalCenter: parent.verticalCenter - color: nextArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: nextArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" visible: playerAvailable opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3 diff --git a/quickshell/Modules/DankBar/Widgets/NotepadButton.qml b/quickshell/Modules/DankBar/Widgets/NotepadButton.qml index e88cbbbd..08f5e6bc 100644 --- a/quickshell/Modules/DankBar/Widgets/NotepadButton.qml +++ b/quickshell/Modules/DankBar/Widgets/NotepadButton.qml @@ -285,7 +285,7 @@ BasePill { width: parent.width height: 30 radius: Theme.cornerRadius - color: tabArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: tabArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" Row { anchors.fill: parent @@ -327,7 +327,7 @@ BasePill { width: parent.width height: 30 radius: Theme.cornerRadius - color: newNoteArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: newNoteArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" Row { anchors.fill: parent diff --git a/quickshell/Modules/DankBar/Widgets/RunningApps.qml b/quickshell/Modules/DankBar/Widgets/RunningApps.qml index 358b877f..6ca6d3e6 100644 --- a/quickshell/Modules/DankBar/Widgets/RunningApps.qml +++ b/quickshell/Modules/DankBar/Widgets/RunningApps.qml @@ -273,7 +273,7 @@ BasePill { if (isFocused) { return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2); } - return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"; + return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"; } // App icon @@ -528,7 +528,7 @@ BasePill { if (isFocused) { return mouseArea.containsMouse ? Theme.primarySelected : Theme.withAlpha(Theme.primary, 0.2); } - return mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent"; + return mouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"; } IconImage { @@ -738,6 +738,15 @@ BasePill { sourceComponent: PanelWindow { id: contextMenuWindow + WindowBlur { + targetWindow: contextMenuWindow + blurX: contextMenuRect.x + blurY: contextMenuRect.y + blurWidth: contextMenuWindow.isVisible ? contextMenuRect.width : 0 + blurHeight: contextMenuWindow.isVisible ? contextMenuRect.height : 0 + blurRadius: Theme.cornerRadius + } + property var currentWindow: null property bool isVisible: false property point anchorPos: Qt.point(0, 0) @@ -830,6 +839,7 @@ BasePill { } Rectangle { + id: contextMenuRect x: { if (contextMenuWindow.isVertical) { if (contextMenuWindow.edge === "left") { @@ -858,13 +868,13 @@ BasePill { height: 32 color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) radius: Theme.cornerRadius - border.width: 1 - border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) + border.width: BlurService.enabled ? BlurService.borderWidth : 1 + border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) Rectangle { anchors.fill: parent radius: parent.radius - color: closeMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: closeMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" } StyledText { diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index a122e2c0..ac04ff34 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -287,7 +287,7 @@ BasePill { height: root.trayItemSize anchors.centerIn: parent radius: Theme.cornerRadius - color: trayItemArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" border.width: dragHandler.dragging ? 2 : 0 border.color: Theme.primary opacity: dragHandler.dragging ? 0.8 : 1.0 @@ -425,7 +425,7 @@ BasePill { height: root.trayItemSize anchors.centerIn: parent radius: Theme.cornerRadius - color: caretArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: caretArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" DankIcon { anchors.centerIn: parent @@ -547,7 +547,7 @@ BasePill { height: root.trayItemSize anchors.centerIn: parent radius: Theme.cornerRadius - color: trayItemArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: trayItemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" border.width: dragHandler.dragging ? 2 : 0 border.color: Theme.primary opacity: dragHandler.dragging ? 0.8 : 1.0 @@ -685,7 +685,7 @@ BasePill { height: root.trayItemSize anchors.centerIn: parent radius: Theme.cornerRadius - color: caretAreaVert.containsMouse ? Theme.widgetBaseHoverColor : "transparent" + color: caretAreaVert.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent" DankIcon { anchors.centerIn: parent @@ -723,6 +723,16 @@ BasePill { PanelWindow { id: overflowMenu + + WindowBlur { + targetWindow: overflowMenu + blurX: menuContainer.x + blurY: menuContainer.y + blurWidth: root.menuOpen ? menuContainer.width : 0 + blurHeight: root.menuOpen ? menuContainer.height : 0 + blurRadius: Theme.cornerRadius + } + visible: root.menuOpen screen: root.parentScreen WlrLayershell.layer: WlrLayershell.Top @@ -990,6 +1000,15 @@ BasePill { layer.samples: 4 } + Rectangle { + anchors.fill: parent + color: "transparent" + radius: Theme.cornerRadius + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + z: 100 + } + Grid { id: menuGrid anchors.centerIn: parent @@ -1030,7 +1049,7 @@ BasePill { width: root.trayItemSize + 4 height: root.trayItemSize + 4 radius: Theme.cornerRadius - color: itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0) + color: itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0) IconImage { id: menuIconImg @@ -1191,6 +1210,15 @@ BasePill { PanelWindow { id: menuWindow + WindowBlur { + targetWindow: menuWindow + blurX: trayMenuContainer.x + blurY: trayMenuContainer.y + blurWidth: menuRoot.showMenu ? trayMenuContainer.width : 0 + blurHeight: menuRoot.showMenu ? trayMenuContainer.height : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: "dms:tray-menu-window" visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false) WlrLayershell.layer: WlrLayershell.Top @@ -1302,7 +1330,7 @@ BasePill { onClicked: mouse => { const clickX = mouse.x + menuWindow.maskX; const clickY = mouse.y + menuWindow.maskY; - const outsideContent = clickX < menuContainer.x || clickX > menuContainer.x + menuContainer.width || clickY < menuContainer.y || clickY > menuContainer.y + menuContainer.height; + const outsideContent = clickX < trayMenuContainer.x || clickX > trayMenuContainer.x + trayMenuContainer.width || clickY < trayMenuContainer.y || clickY > trayMenuContainer.y + trayMenuContainer.height; if (!outsideContent) return; @@ -1360,7 +1388,7 @@ BasePill { } Item { - id: menuContainer + id: trayMenuContainer readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2)) readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2) @@ -1438,6 +1466,15 @@ BasePill { layer.textureMirroring: ShaderEffectSource.MirrorVertically } + Rectangle { + anchors.fill: parent + color: "transparent" + radius: Theme.cornerRadius + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + z: 100 + } + QsMenuAnchor { id: submenuHydrator anchor.window: menuWindow @@ -1470,7 +1507,7 @@ BasePill { width: parent.width height: 28 radius: Theme.cornerRadius - color: visibilityToggleArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0) + color: visibilityToggleArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0) StyledText { anchors.left: parent.left @@ -1523,7 +1560,7 @@ BasePill { width: parent.width height: 28 radius: Theme.cornerRadius - color: backArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0) + color: backArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0) Row { anchors.left: parent.left @@ -1574,7 +1611,7 @@ BasePill { color: { if (menuEntry?.isSeparator) return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2); - return itemArea.containsMouse ? Theme.widgetBaseHoverColor : Theme.withAlpha(Theme.surfaceContainer, 0); + return itemArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.withAlpha(Theme.surfaceContainer, 0); } MouseArea { diff --git a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml index ad1ec146..2e2a538a 100644 --- a/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml +++ b/quickshell/Modules/DankBar/Widgets/WorkspaceSwitcher.qml @@ -17,6 +17,7 @@ Item { property real widgetHeight: 30 property real barThickness: 48 property var barConfig: null + property var blurBarWindow: null property var hyprlandOverviewLoader: null property var parentScreen: null property int _desktopEntriesUpdateTrigger: 0 @@ -1845,5 +1846,27 @@ Item { if (useExtWorkspace && !DMSService.activeSubscriptions.includes("extworkspace")) { DMSService.addSubscription("extworkspace"); } + _updateBlurRegistration(); + } + + property bool _blurRegistered: false + readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0 + + on_ShouldBlurChanged: _updateBlurRegistration() + + function _updateBlurRegistration() { + if (_shouldBlur && !_blurRegistered) { + blurBarWindow.registerBlurWidget(visualBackground); + _blurRegistered = true; + } else if (!_shouldBlur && _blurRegistered) { + if (blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualBackground); + _blurRegistered = false; + } + } + + Component.onDestruction: { + if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualBackground); } } diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index b16a18fe..136f23ad 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -17,6 +17,15 @@ Variants { delegate: PanelWindow { id: dock + WindowBlur { + targetWindow: dock + blurX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x + blurY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y + blurWidth: dock.hasApps && dock.reveal ? dockBackground.width : 0 + blurHeight: dock.hasApps && dock.reveal ? dockBackground.height : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: "dms:dock" readonly property bool isVertical: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right @@ -562,6 +571,15 @@ Variants { color: Theme.withAlpha(Theme.surfaceContainer, backgroundTransparency) radius: Theme.cornerRadius } + + Rectangle { + anchors.fill: parent + color: "transparent" + radius: Theme.cornerRadius + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + z: 100 + } } Shape { diff --git a/quickshell/Modules/Dock/DockContextMenu.qml b/quickshell/Modules/Dock/DockContextMenu.qml index 375e21c9..183f3317 100644 --- a/quickshell/Modules/Dock/DockContextMenu.qml +++ b/quickshell/Modules/Dock/DockContextMenu.qml @@ -9,6 +9,15 @@ import qs.Widgets PanelWindow { id: root + WindowBlur { + targetWindow: root + blurX: menuContainer.x + blurY: menuContainer.y + blurWidth: root.visible ? menuContainer.width : 0 + blurHeight: root.visible ? menuContainer.height : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: "dms:dock-context-menu" property var appData: null @@ -168,8 +177,8 @@ PanelWindow { height: menuColumn.implicitHeight + Theme.spacingS * 2 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: 1 + border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: BlurService.enabled ? BlurService.borderWidth : 1 opacity: root.visible ? 1 : 0 visible: opacity > 0 diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 823106f1..8c9f7f44 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -1,6 +1,5 @@ import QtQuick import QtQuick.Controls -import QtQuick.Effects import Quickshell import Quickshell.Wayland import Quickshell.Services.Notifications @@ -11,6 +10,15 @@ import qs.Widgets PanelWindow { id: win + WindowBlur { + targetWindow: win + blurX: content.x + content.cardInset + swipeTx.x + tx.x + blurY: content.y + content.cardInset + swipeTx.y + tx.y + blurWidth: !win._finalized ? Math.max(0, content.width - content.cardInset * 2) : 0 + blurHeight: !win._finalized ? Math.max(0, content.height - content.cardInset * 2) : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: "dms:notification-popup" required property var notificationData @@ -436,6 +444,16 @@ PanelWindow { } } + Rectangle { + anchors.fill: parent + anchors.margins: content.cardInset + radius: Theme.cornerRadius + color: "transparent" + border.color: BlurService.borderColor + border.width: BlurService.borderWidth + z: 100 + } + Item { id: backgroundContainer anchors.fill: parent diff --git a/quickshell/Modules/Plugins/BasePill.qml b/quickshell/Modules/Plugins/BasePill.qml index 5b861e4f..7dea95a3 100644 --- a/quickshell/Modules/Plugins/BasePill.qml +++ b/quickshell/Modules/Plugins/BasePill.qml @@ -14,6 +14,7 @@ Item { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property alias content: contentLoader.sourceComponent property bool isVerticalOrientation: axis?.isVertical ?? false property bool isFirst: false @@ -106,7 +107,7 @@ Item { const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0; const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false)); const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency; - const baseColor = isHovered ? Theme.widgetBaseHoverColor : Theme.widgetBaseBackgroundColor; + const baseColor = isHovered ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.widgetBaseBackgroundColor; if (Theme.widgetBackgroundHasAlpha) { return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency); @@ -169,4 +170,26 @@ Item { root.wheel(wheelEvent); } } + + property bool _blurRegistered: false + readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0 + + on_ShouldBlurChanged: _updateBlurRegistration() + + function _updateBlurRegistration() { + if (_shouldBlur && !_blurRegistered) { + blurBarWindow.registerBlurWidget(visualContent); + _blurRegistered = true; + } else if (!_shouldBlur && _blurRegistered) { + if (blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualContent); + _blurRegistered = false; + } + } + + Component.onCompleted: _updateBlurRegistration() + Component.onDestruction: { + if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualContent); + } } diff --git a/quickshell/Modules/Plugins/PluginComponent.qml b/quickshell/Modules/Plugins/PluginComponent.qml index eaed6040..85dd32da 100644 --- a/quickshell/Modules/Plugins/PluginComponent.qml +++ b/quickshell/Modules/Plugins/PluginComponent.qml @@ -14,6 +14,7 @@ Item { property real barThickness: 48 property real barSpacing: 4 property var barConfig: null + property var blurBarWindow: null property string pluginId: "" property var pluginService: null @@ -182,6 +183,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow content: root.horizontalBarPill states: State { @@ -241,6 +243,7 @@ Item { barThickness: root.barThickness barSpacing: root.barSpacing barConfig: root.barConfig + blurBarWindow: root.blurBarWindow content: root.verticalBarPill isVerticalOrientation: true diff --git a/quickshell/Modules/ProcessList/ProcessContextMenu.qml b/quickshell/Modules/ProcessList/ProcessContextMenu.qml index 8dfba1ed..7c48b5a9 100644 --- a/quickshell/Modules/ProcessList/ProcessContextMenu.qml +++ b/quickshell/Modules/ProcessList/ProcessContextMenu.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import Quickshell import qs.Common +import qs.Services import qs.Widgets Popup { @@ -186,8 +187,8 @@ Popup { contentItem: Rectangle { 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: 1 + border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) + border.width: BlurService.enabled ? BlurService.borderWidth : 1 Item { id: keyboardHandler diff --git a/quickshell/Modules/Settings/ThemeColorsTab.qml b/quickshell/Modules/Settings/ThemeColorsTab.qml index 80e92e53..ce080c44 100644 --- a/quickshell/Modules/Settings/ThemeColorsTab.qml +++ b/quickshell/Modules/Settings/ThemeColorsTab.qml @@ -125,6 +125,15 @@ Item { return Theme.warning; } + function openBlurBorderColorPicker() { + PopoutService.colorPickerModal.selectedColor = SettingsData.blurBorderCustomColor ?? "#ffffff"; + PopoutService.colorPickerModal.pickerTitle = I18n.tr("Blur Border Color"); + PopoutService.colorPickerModal.onColorSelectedCallback = function (color) { + SettingsData.set("blurBorderCustomColor", color.toString()); + }; + PopoutService.colorPickerModal.open(); + } + function openM3ShadowColorPicker() { PopoutService.colorPickerModal.selectedColor = SettingsData.m3ElevationCustomColor ?? "#000000"; PopoutService.colorPickerModal.pickerTitle = I18n.tr("Shadow Color"); @@ -1816,6 +1825,77 @@ Item { } } + SettingsCard { + tab: "theme" + tags: ["blur", "background", "transparency", "glass", "frosted"] + title: I18n.tr("Background Blur") + settingKey: "blurEnabled" + iconName: "blur_on" + + SettingsToggleRow { + tab: "theme" + tags: ["blur", "background", "transparency", "glass", "frosted"] + settingKey: "blurEnabled" + text: I18n.tr("Background Blur") + description: BlurService.available ? I18n.tr("Blur the background behind bars, popouts, modals, and notifications. Requires compositor support and configuration.") : I18n.tr("Requires a newer version of Quickshell") + checked: SettingsData.blurEnabled ?? false + enabled: BlurService.available + onToggled: checked => SettingsData.set("blurEnabled", checked) + } + + SettingsDropdownRow { + tab: "theme" + tags: ["blur", "border", "outline", "edge"] + settingKey: "blurBorderColor" + text: I18n.tr("Blur Border Color") + description: I18n.tr("Border color around blurred surfaces") + visible: SettingsData.blurEnabled + options: [I18n.tr("Outline", "blur border color"), I18n.tr("Primary", "blur border color"), I18n.tr("Secondary", "blur border color"), I18n.tr("Text Color", "blur border color"), I18n.tr("Custom", "blur border color")] + currentValue: { + switch (SettingsData.blurBorderColor) { + case "primary": + return I18n.tr("Primary", "blur border color"); + case "secondary": + return I18n.tr("Secondary", "blur border color"); + case "surfaceText": + return I18n.tr("Text Color", "blur border color"); + case "custom": + return I18n.tr("Custom", "blur border color"); + default: + return I18n.tr("Outline", "blur border color"); + } + } + onValueChanged: value => { + if (value === I18n.tr("Primary", "blur border color")) { + SettingsData.set("blurBorderColor", "primary"); + } else if (value === I18n.tr("Secondary", "blur border color")) { + SettingsData.set("blurBorderColor", "secondary"); + } else if (value === I18n.tr("Text Color", "blur border color")) { + SettingsData.set("blurBorderColor", "surfaceText"); + } else if (value === I18n.tr("Custom", "blur border color")) { + SettingsData.set("blurBorderColor", "custom"); + openBlurBorderColorPicker(); + } else { + SettingsData.set("blurBorderColor", "outline"); + } + } + } + + SettingsSliderRow { + tab: "theme" + tags: ["blur", "border", "opacity"] + settingKey: "blurBorderOpacity" + text: I18n.tr("Blur Border Opacity") + visible: SettingsData.blurEnabled + value: Math.round((SettingsData.blurBorderOpacity ?? 1.0) * 100) + minimum: 0 + maximum: 100 + unit: "%" + defaultValue: 100 + onSliderValueChanged: newValue => SettingsData.set("blurBorderOpacity", newValue / 100) + } + } + SettingsCard { tab: "theme" tags: ["niri", "layout", "gaps", "radius", "window", "border"] @@ -2602,7 +2682,6 @@ Item { onToggled: checked => SettingsData.set("matugenTemplateNeovim", checked) } - SettingsDropdownRow { text: I18n.tr("Dark mode base") tab: "theme" diff --git a/quickshell/Services/BlurService.qml b/quickshell/Services/BlurService.qml new file mode 100644 index 00000000..535d8d6b --- /dev/null +++ b/quickshell/Services/BlurService.qml @@ -0,0 +1,88 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Wayland // ! Import is needed despite what qmlls says +import qs.Common + +Singleton { + id: root + + property bool available: false + readonly property bool enabled: available && (SettingsData.blurEnabled ?? false) + + readonly property color borderColor: { + if (!enabled) + return "transparent"; + const opacity = SettingsData.blurBorderOpacity ?? 0.5; + switch (SettingsData.blurBorderColor ?? "outline") { + case "primary": + return Theme.withAlpha(Theme.primary, opacity); + case "secondary": + return Theme.withAlpha(Theme.secondary, opacity); + case "surfaceText": + return Theme.withAlpha(Theme.surfaceText, opacity); + case "custom": + return Theme.withAlpha(SettingsData.blurBorderCustomColor ?? "#ffffff", opacity); + default: + return Theme.withAlpha(Theme.outline, opacity); + } + } + readonly property int borderWidth: enabled ? 1 : 0 + + function hoverColor(baseColor, hoverAlpha) { + if (!enabled) + return baseColor; + return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15); + } + + function createBlurRegion(targetWindow) { + if (!available) + return null; + + try { + const region = Qt.createQmlObject(` + import Quickshell + Region {} + `, targetWindow, "BlurRegion"); + targetWindow.BackgroundEffect.blurRegion = region; + return region; + } catch (e) { + console.warn("BlurService: Failed to create blur region:", e); + return null; + } + } + + function reapplyBlurRegion(targetWindow, region) { + if (!region || !available) + return; + try { + targetWindow.BackgroundEffect.blurRegion = region; + region.changed(); + } catch (e) {} + } + + function destroyBlurRegion(targetWindow, region) { + if (!region) + return; + try { + targetWindow.BackgroundEffect.blurRegion = null; + } catch (e) {} + region.destroy(); + } + + Component.onCompleted: { + try { + const test = Qt.createQmlObject(` + import Quickshell + Region { radius: 0 } + `, root, "BlurAvailabilityTest"); + test.destroy(); + available = true; + console.info("BlurService: Initialized with blur support"); + } catch (e) { + console.info("BlurService: BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell."); + } + } +} diff --git a/quickshell/Widgets/DankOSD.qml b/quickshell/Widgets/DankOSD.qml index 5a83562f..e254094b 100644 --- a/quickshell/Widgets/DankOSD.qml +++ b/quickshell/Widgets/DankOSD.qml @@ -107,6 +107,16 @@ PanelWindow { } WlrLayershell.exclusiveZone: -1 WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + WindowBlur { + targetWindow: root + blurX: shadowBuffer + blurY: shadowBuffer + blurWidth: shouldBeVisible ? alignedWidth : 0 + blurHeight: shouldBeVisible ? alignedHeight : 0 + blurRadius: Theme.cornerRadius + } + color: "transparent" readonly property real dpr: CompositorService.getScreenScale(screen) @@ -263,8 +273,8 @@ PanelWindow { anchors.fill: parent radius: Theme.cornerRadius color: Theme.withAlpha(Theme.surfaceContainer, osdContainer.popupSurfaceAlpha) - border.color: Theme.outlineMedium - border.width: 1 + border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium + border.width: BlurService.enabled ? BlurService.borderWidth : 1 z: -1 } diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index dce68c18..9163a27c 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -398,6 +398,17 @@ Item { visible: false color: "transparent" + WindowBlur { + id: popoutBlur + targetWindow: contentWindow + readonly property real s: Math.min(1, contentContainer.scaleValue) + blurX: contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) + blurY: contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) + blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.width * s : 0 + blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) ? contentContainer.height * s : 0 + blurRadius: Theme.cornerRadius + } + WlrLayershell.namespace: root.layerNamespace WlrLayershell.layer: { switch (Quickshell.env("DMS_POPOUT_LAYER")) { @@ -569,8 +580,8 @@ Item { anchors.fill: parent radius: Theme.cornerRadius color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) - border.color: Theme.outlineMedium - border.width: 0 + border.color: BlurService.enabled ? BlurService.borderColor : Theme.outlineMedium + border.width: BlurService.borderWidth } Loader { diff --git a/quickshell/Widgets/WindowBlur.qml b/quickshell/Widgets/WindowBlur.qml new file mode 100644 index 00000000..3b45212b --- /dev/null +++ b/quickshell/Widgets/WindowBlur.qml @@ -0,0 +1,65 @@ +import QtQuick +import qs.Services + +Item { + id: root + + visible: false + + required property var targetWindow + property var blurItem: null + property real blurX: 0 + property real blurY: 0 + property real blurWidth: 0 + property real blurHeight: 0 + property real blurRadius: 0 + + property var _region: null + + function _apply() { + if (!BlurService.enabled || !targetWindow) { + _cleanup(); + return; + } + + if (!_region) + _region = BlurService.createBlurRegion(targetWindow); + + if (!_region) + return; + + _region.item = Qt.binding(() => root.blurItem); + _region.x = Qt.binding(() => root.blurX); + _region.y = Qt.binding(() => root.blurY); + _region.width = Qt.binding(() => root.blurWidth); + _region.height = Qt.binding(() => root.blurHeight); + _region.radius = Qt.binding(() => root.blurRadius); + } + + function _cleanup() { + if (!_region) + return; + BlurService.destroyBlurRegion(targetWindow, _region); + _region = null; + } + + Connections { + target: BlurService + function onEnabledChanged() { + root._apply(); + } + } + + Connections { + target: root.targetWindow + function onVisibleChanged() { + if (root.targetWindow && root.targetWindow.visible) { + root._region = null; + root._apply(); + } + } + } + + Component.onCompleted: _apply() + Component.onDestruction: _cleanup() +}