1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

(frame): Add blur support & cleanup

This commit is contained in:
purian23
2026-03-31 21:25:51 -04:00
parent e913630f90
commit 1217b25de5
8 changed files with 232 additions and 78 deletions

View File

@@ -26,13 +26,13 @@ Item {
readonly property real _frameLeftInset: {
if (!SettingsData.frameEnabled || barWindow.isVertical) return 0
return barWindow.hasAdjacentLeftBar
? SettingsData.frameBarThickness
? SettingsData.frameBarSize
: 0
}
readonly property real _frameRightInset: {
if (!SettingsData.frameEnabled || barWindow.isVertical) return 0
return barWindow.hasAdjacentRightBar
? SettingsData.frameBarThickness
? SettingsData.frameBarSize
: 0
}
readonly property real _frameTopInset: {

View File

@@ -133,6 +133,11 @@ PanelWindow {
teardown();
if (!BlurService.enabled || !BlurService.available)
return;
// In frame mode, FrameWindow owns the blur region for the entire screen edge
// (including the bar area). The bar must not set its own competing blur region
// so that frameBlurEnabled acts as the single control for all blur in frame mode.
if (SettingsData.frameEnabled)
return;
const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0);
const hasBar = barHasTransparency;
@@ -187,6 +192,11 @@ PanelWindow {
}
}
Connections {
target: SettingsData
function onFrameEnabledChanged() { barBlur.rebuild(); }
}
Connections {
target: topBarSlide
function onXChanged() {
@@ -238,7 +248,7 @@ PanelWindow {
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property string _barId: barConfig?.id ?? "default"
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
readonly property color _bgColor: (SettingsData.frameEnabled && SettingsData.frameSyncBarColor)
readonly property color _bgColor: SettingsData.frameEnabled
? Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity)
: Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
@@ -398,7 +408,7 @@ PanelWindow {
readonly property int notificationCount: NotificationService.notifications.length
readonly property real effectiveBarThickness: SettingsData.frameEnabled
? SettingsData.frameBarThickness
? SettingsData.frameBarSize
: Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr)
readonly property bool effectiveOpenOnOverview: SettingsData.frameEnabled
? SettingsData.frameShowOnOverview

View File

@@ -9,18 +9,24 @@ Item {
anchors.fill: parent
required property var barEdges
readonly property real _thickness: SettingsData.frameThickness
readonly property real _barThickness: SettingsData.frameBarThickness
readonly property real _rounding: SettingsData.frameRounding
required property real cutoutTopInset
required property real cutoutBottomInset
required property real cutoutLeftInset
required property real cutoutRightInset
required property real cutoutRadius
Rectangle {
id: borderRect
anchors.fill: parent
color: SettingsData.effectiveFrameColor
opacity: SettingsData.frameOpacity
// Bake frameOpacity into the color alpha rather than using the `opacity` property.
// Qt Quick can skip layer.effect processing on items with opacity < 1 as an
// optimization, causing the MultiEffect inverted mask to stop working and the
// Rectangle to render as a plain square at low opacity values.
color: Qt.rgba(SettingsData.effectiveFrameColor.r,
SettingsData.effectiveFrameColor.g,
SettingsData.effectiveFrameColor.b,
SettingsData.frameOpacity)
layer.enabled: true
layer.effect: MultiEffect {
@@ -42,12 +48,12 @@ Item {
Rectangle {
anchors {
fill: parent
topMargin: root.barEdges.includes("top") ? root._barThickness : root._thickness
bottomMargin: root.barEdges.includes("bottom") ? root._barThickness : root._thickness
leftMargin: root.barEdges.includes("left") ? root._barThickness : root._thickness
rightMargin: root.barEdges.includes("right") ? root._barThickness : root._thickness
topMargin: root.cutoutTopInset
bottomMargin: root.cutoutBottomInset
leftMargin: root.cutoutLeftInset
rightMargin: root.cutoutRightInset
}
radius: root._rounding
radius: root.cutoutRadius
}
}
}

View File

@@ -4,6 +4,7 @@ import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
PanelWindow {
id: win
@@ -29,9 +30,140 @@ PanelWindow {
// No input — pass everything through to apps and bar
mask: Region {}
readonly property var barEdges: {
SettingsData.barConfigs;
return SettingsData.getActiveBarEdgesForScreen(win.screen);
}
readonly property real _dpr: CompositorService.getScreenScale(win.screen)
readonly property bool _frameActive: SettingsData.frameEnabled
&& SettingsData.isScreenInPreferences(win.screen, SettingsData.frameScreenPreferences)
readonly property int _windowRegionWidth: win._regionInt(win.width)
readonly property int _windowRegionHeight: win._regionInt(win.height)
function _regionInt(value) {
return Math.max(0, Math.round(Theme.px(value, win._dpr)));
}
readonly property int cutoutTopInset: win._regionInt(barEdges.includes("top") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutBottomInset: win._regionInt(barEdges.includes("bottom") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutLeftInset: win._regionInt(barEdges.includes("left") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutRightInset: win._regionInt(barEdges.includes("right") ? SettingsData.frameBarSize : SettingsData.frameThickness)
readonly property int cutoutWidth: Math.max(0, win._windowRegionWidth - win.cutoutLeftInset - win.cutoutRightInset)
readonly property int cutoutHeight: Math.max(0, win._windowRegionHeight - win.cutoutTopInset - win.cutoutBottomInset)
readonly property int cutoutRadius: {
const requested = win._regionInt(SettingsData.frameRounding);
const maxRadius = Math.floor(Math.min(win.cutoutWidth, win.cutoutHeight) / 2);
return Math.max(0, Math.min(requested, maxRadius));
}
// Slightly expand the subtractive blur cutout at very low opacity levels
readonly property int _blurCutoutCompensation: SettingsData.frameOpacity <= 0.2 ? 1 : 0
readonly property int _blurCutoutLeft: Math.max(0, win.cutoutLeftInset - win._blurCutoutCompensation)
readonly property int _blurCutoutTop: Math.max(0, win.cutoutTopInset - win._blurCutoutCompensation)
readonly property int _blurCutoutRight: Math.min(win._windowRegionWidth, win._windowRegionWidth - win.cutoutRightInset + win._blurCutoutCompensation)
readonly property int _blurCutoutBottom: Math.min(win._windowRegionHeight, win._windowRegionHeight - win.cutoutBottomInset + win._blurCutoutCompensation)
readonly property int _blurCutoutRadius: {
const requested = win.cutoutRadius + win._blurCutoutCompensation;
const maxRadius = Math.floor(Math.min(_blurCutout.width, _blurCutout.height) / 2);
return Math.max(0, Math.min(requested, maxRadius));
}
// Must stay visible so Region.item can resolve scene coordinates.
Item {
id: _blurCutout
x: win._blurCutoutLeft
y: win._blurCutoutTop
width: Math.max(0, win._blurCutoutRight - win._blurCutoutLeft)
height: Math.max(0, win._blurCutoutBottom - win._blurCutoutTop)
}
property var _frameBlurRegion: null
function _buildBlur() {
_teardownBlur();
// Follow the global blur toggle
if (!BlurService.enabled || !SettingsData.frameBlurEnabled || !win._frameActive || !win.visible)
return;
try {
const region = Qt.createQmlObject(
'import QtQuick; import Quickshell; Region {' +
' property Item cutoutItem;' +
' property int cutoutRadius: 0;' +
' Region {' +
' item: cutoutItem;' +
' intersection: Intersection.Subtract;' +
' radius: cutoutRadius;' +
' }' +
'}',
win, "FrameBlurRegion");
region.x = Qt.binding(() => 0);
region.y = Qt.binding(() => 0);
region.width = Qt.binding(() => win._windowRegionWidth);
region.height = Qt.binding(() => win._windowRegionHeight);
region.cutoutItem = _blurCutout;
region.cutoutRadius = Qt.binding(() => win._blurCutoutRadius);
win.BackgroundEffect.blurRegion = region;
win._frameBlurRegion = region;
} catch (e) {
console.warn("FrameWindow: Failed to create blur region:", e);
}
}
function _teardownBlur() {
if (!win._frameBlurRegion)
return;
try {
win.BackgroundEffect.blurRegion = null;
} catch (e) {}
win._frameBlurRegion.destroy();
win._frameBlurRegion = null;
}
Timer {
id: _blurRebuildTimer
interval: 1
onTriggered: win._buildBlur()
}
Connections {
target: SettingsData
function onFrameBlurEnabledChanged() { _blurRebuildTimer.restart(); }
function onFrameEnabledChanged() { _blurRebuildTimer.restart(); }
function onFrameThicknessChanged() { _blurRebuildTimer.restart(); }
function onFrameBarSizeChanged() { _blurRebuildTimer.restart(); }
function onFrameOpacityChanged() { _blurRebuildTimer.restart(); }
function onFrameRoundingChanged() { _blurRebuildTimer.restart(); }
function onFrameScreenPreferencesChanged() { _blurRebuildTimer.restart(); }
function onBarConfigsChanged() { _blurRebuildTimer.restart(); }
}
Connections {
target: BlurService
function onEnabledChanged() { _blurRebuildTimer.restart(); }
}
onVisibleChanged: {
if (visible) {
win._frameBlurRegion = null;
_blurRebuildTimer.restart();
} else {
_teardownBlur();
}
}
Component.onCompleted: Qt.callLater(() => win._buildBlur())
Component.onDestruction: win._teardownBlur()
FrameBorder {
anchors.fill: parent
visible: SettingsData.frameEnabled && SettingsData.isScreenInPreferences(win.screen, SettingsData.frameScreenPreferences)
barEdges: { SettingsData.barConfigs; return SettingsData.getActiveBarEdgesForScreen(win.screen); }
visible: win._frameActive
cutoutTopInset: win.cutoutTopInset
cutoutBottomInset: win.cutoutBottomInset
cutoutLeftInset: win.cutoutLeftInset
cutoutRightInset: win.cutoutRightInset
cutoutRadius: win.cutoutRadius
}
}

View File

@@ -55,7 +55,7 @@ Item {
id: roundingSlider
settingKey: "frameRounding"
tags: ["frame", "border", "rounding", "radius", "corner"]
text: I18n.tr("Border rounding")
text: I18n.tr("Border Radius")
unit: "px"
minimum: 0
maximum: 100
@@ -75,7 +75,7 @@ Item {
id: thicknessSlider
settingKey: "frameThickness"
tags: ["frame", "border", "thickness", "size", "width"]
text: I18n.tr("Border thickness")
text: I18n.tr("Border Width")
unit: "px"
minimum: 2
maximum: 100
@@ -93,22 +93,22 @@ Item {
SettingsSliderRow {
id: barThicknessSlider
settingKey: "frameBarThickness"
settingKey: "frameBarSize"
tags: ["frame", "bar", "thickness", "size", "height", "width"]
text: I18n.tr("Bar-edge thickness")
text: I18n.tr("Size")
description: I18n.tr("Height of horizontal bars / width of vertical bars in frame mode")
unit: "px"
minimum: 24
maximum: 100
step: 1
defaultValue: 42
value: SettingsData.frameBarThickness
onSliderDragFinished: v => SettingsData.set("frameBarThickness", v)
defaultValue: 40
value: SettingsData.frameBarSize
onSliderDragFinished: v => SettingsData.set("frameBarSize", v)
Binding {
target: barThicknessSlider
property: "value"
value: SettingsData.frameBarThickness
value: SettingsData.frameBarSize
}
}
@@ -131,6 +131,50 @@ Item {
}
}
SettingsToggleRow {
id: frameBlurToggle
settingKey: "frameBlurEnabled"
tags: ["frame", "blur", "background", "glass", "transparency", "frosted"]
text: I18n.tr("Frame Blur")
description: !BlurService.available
? I18n.tr("Requires a newer version of Quickshell")
: I18n.tr("Apply compositor blur behind the frame border")
checked: SettingsData.frameBlurEnabled
onToggled: checked => SettingsData.set("frameBlurEnabled", checked)
enabled: BlurService.available && SettingsData.blurEnabled
opacity: enabled ? 1.0 : 0.5
visible: BlurService.available
}
Item {
visible: BlurService.available && !SettingsData.blurEnabled
width: parent.width
height: blurToggleNote.height + Theme.spacingM * 2
Row {
id: blurToggleNote
x: Theme.spacingM
width: parent.width - Theme.spacingM * 2
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "blur_on"
size: Theme.fontSizeMedium
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Frame Blur is controlled by Background Blur in Theme & Colors")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width - Theme.fontSizeMedium - Theme.spacingS
}
}
}
// Color mode buttons
SettingsButtonGroupRow {
settingKey: "frameColor"
@@ -217,17 +261,9 @@ Item {
title: I18n.tr("Bar Integration")
settingKey: "frameBarIntegration"
collapsible: true
expanded: false
visible: SettingsData.frameEnabled
SettingsToggleRow {
settingKey: "frameSyncBarColor"
tags: ["frame", "bar", "sync", "color", "background"]
text: I18n.tr("Sync bar background to frame")
description: I18n.tr("Sets the bar background color to match the frame border color for a seamless look")
checked: SettingsData.frameSyncBarColor
onToggled: checked => SettingsData.set("frameSyncBarColor", checked)
}
SettingsToggleRow {
visible: CompositorService.isNiri
settingKey: "frameShowOnOverview"
@@ -246,6 +282,7 @@ Item {
title: I18n.tr("Display Assignment")
settingKey: "frameDisplays"
collapsible: true
expanded: false
visible: SettingsData.frameEnabled
SettingsDisplayPicker {