From e95c80011f9ad13d4f3de6be7715158c42630d09 Mon Sep 17 00:00:00 2001 From: purian23 Date: Wed, 10 Jun 2026 09:34:42 -0400 Subject: [PATCH] Refactor shadow handling & improve connected chrome rendering --- quickshell/Common/ElevationShadow.qml | 54 +- quickshell/Common/Theme.qml | 13 + quickshell/Modules/DankBar/BarCanvas.qml | 3 +- .../Modules/DankBar/Widgets/SystemTrayBar.qml | 14 +- .../Modules/DankDash/MediaDropdownOverlay.qml | 6 +- quickshell/Modules/Frame/FrameBorder.qml | 49 +- quickshell/Modules/Frame/FrameWindow.qml | 517 +++--------------- .../Center/HistoryNotificationCard.qml | 2 +- .../Notifications/Center/NotificationCard.qml | 2 +- .../Notifications/Popup/NotificationPopup.qml | 22 +- quickshell/Shaders/frag/connected_arc.frag | 61 ++- quickshell/Shaders/frag/connected_chrome.frag | 71 +++ quickshell/Shaders/frag/elevation_rect.frag | 60 ++ quickshell/Shaders/frag/frame_arc.frag | 44 ++ quickshell/Shaders/qsb/connected_arc.frag.qsb | Bin 5954 -> 7880 bytes .../Shaders/qsb/connected_chrome.frag.qsb | Bin 0 -> 4833 bytes .../Shaders/qsb/elevation_rect.frag.qsb | Bin 0 -> 4215 bytes quickshell/Shaders/qsb/frame_arc.frag.qsb | Bin 0 -> 3124 bytes quickshell/Widgets/DankOSD.qml | 2 - quickshell/Widgets/DankPopoutConnected.qml | 132 +++-- 20 files changed, 434 insertions(+), 618 deletions(-) create mode 100644 quickshell/Shaders/frag/connected_chrome.frag create mode 100644 quickshell/Shaders/frag/elevation_rect.frag create mode 100644 quickshell/Shaders/frag/frame_arc.frag create mode 100644 quickshell/Shaders/qsb/connected_chrome.frag.qsb create mode 100644 quickshell/Shaders/qsb/elevation_rect.frag.qsb create mode 100644 quickshell/Shaders/qsb/frame_arc.frag.qsb diff --git a/quickshell/Common/ElevationShadow.qml b/quickshell/Common/ElevationShadow.qml index 1ccf6b1c..9260f6e5 100644 --- a/quickshell/Common/ElevationShadow.qml +++ b/quickshell/Common/ElevationShadow.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects import qs.Common Item { @@ -19,7 +18,12 @@ Item { property real bottomRightRadius: targetRadius property color borderColor: "transparent" property real borderWidth: 0 - property bool useCustomSource: false + + // Rounded-rect geometry within the item; defaults fill the item. + property real sourceX: 0 + property real sourceY: 0 + property real sourceWidth: width + property real sourceHeight: height property bool shadowEnabled: Theme.elevationEnabled property real shadowBlurPx: level && level.blurPx !== undefined ? level.blurPx : 0 @@ -28,36 +32,26 @@ Item { property real shadowOffsetY: Theme.elevationOffsetYFor(level, direction, fallbackOffset) property color shadowColor: Theme.elevationShadowColor(level) property real shadowOpacity: 1 - property real blurMax: Theme.elevationBlurMax - property alias sourceRect: sourceRect + readonly property var _ambient: Theme.elevationAmbient(level) + readonly property real _pad: shadowEnabled ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), _ambient.blurPx + _ambient.spreadPx) + 2) : 0 - layer.enabled: shadowEnabled - - layer.effect: MultiEffect { - autoPaddingEnabled: true - shadowEnabled: true - blurEnabled: false - maskEnabled: false - shadowBlur: Math.max(0, Math.min(1, root.shadowBlurPx / Math.max(1, root.blurMax))) - shadowScale: 1 + (2 * root.shadowSpreadPx) / Math.max(1, Math.min(root.width, root.height)) - shadowHorizontalOffset: root.shadowOffsetX - shadowVerticalOffset: root.shadowOffsetY - blurMax: root.blurMax - shadowColor: root.shadowColor - shadowOpacity: root.shadowOpacity - } - - Rectangle { - id: sourceRect + // Fill + border + key/ambient shadows drawn analytically on one oversized + // quad — no FBO, no blur passes. + ShaderEffect { anchors.fill: parent - visible: !root.useCustomSource - topLeftRadius: root.topLeftRadius - topRightRadius: root.topRightRadius - bottomLeftRadius: root.bottomLeftRadius - bottomRightRadius: root.bottomRightRadius - color: root.targetColor - border.color: root.borderColor - border.width: root.borderWidth + anchors.margins: -root._pad + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/elevation_rect.frag.qsb") + + property real widthPx: width + property real heightPx: height + property real borderWidth: root.borderWidth + property vector4d rectPx: Qt.vector4d(root._pad + root.sourceX, root._pad + root.sourceY, root.sourceWidth, root.sourceHeight) + property vector4d cornerRadius: Qt.vector4d(root.topLeftRadius, root.topRightRadius, root.bottomRightRadius, root.bottomLeftRadius) + property vector4d fillColor: Qt.vector4d(root.targetColor.r, root.targetColor.g, root.targetColor.b, root.targetColor.a) + property vector4d borderColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a) + property vector4d shadowColor: Qt.vector4d(root.shadowColor.r, root.shadowColor.g, root.shadowColor.b, root.shadowEnabled ? root.shadowColor.a * root.shadowOpacity : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, root.shadowBlurPx), root.shadowSpreadPx, root.shadowOffsetX, root.shadowOffsetY) + property vector4d ambientParam: Qt.vector4d(root._ambient.blurPx, root._ambient.spreadPx, root.shadowEnabled ? root._ambient.alpha * root.shadowOpacity : 0, 0) } } diff --git a/quickshell/Common/Theme.qml b/quickshell/Common/Theme.qml index 658c8afe..22124e89 100644 --- a/quickshell/Common/Theme.qml +++ b/quickshell/Common/Theme.qml @@ -911,6 +911,19 @@ Singleton { } return Qt.rgba(r, g, b, alpha); } + // Non-directional ambient layer of the M3 two-part shadow model (key + + // ambient). Derived from the key level so user intensity/opacity settings + // scale both layers together. + function elevationAmbient(level) { + const blur = (level && level.blurPx !== undefined) ? Math.max(0, level.blurPx) : 0; + const alpha = ((level && level.alpha !== undefined) ? level.alpha : 0.3) * 0.5; + return { + blurPx: blur * 1.75, + spreadPx: 1, + alpha: alpha + }; + } + function elevationTintOpacity(level) { if (!level) return 0; diff --git a/quickshell/Modules/DankBar/BarCanvas.qml b/quickshell/Modules/DankBar/BarCanvas.qml index 26a2be2b..f82db6c0 100644 --- a/quickshell/Modules/DankBar/BarCanvas.qml +++ b/quickshell/Modules/DankBar/BarCanvas.qml @@ -61,7 +61,7 @@ Item { // M3 elevation shadow — Level 2 baseline (navigation bar), with per-bar override support readonly property bool hasPerBarOverride: (barConfig?.shadowIntensity ?? 0) > 0 readonly property var elevLevel: Theme.elevationLevel2 - readonly property bool shadowEnabled: !BlurService.enabled && ((Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride) + readonly property bool shadowEnabled: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || hasPerBarOverride readonly property string autoBarShadowDirection: isTop ? "top" : (isBottom ? "bottom" : (isLeft ? "left" : (isRight ? "right" : "top"))) readonly property string globalShadowDirection: Theme.elevationLightDirection === "autoBar" ? autoBarShadowDirection : Theme.elevationLightDirection readonly property string perBarShadowDirectionMode: barConfig?.shadowDirectionMode ?? "inherit" @@ -207,7 +207,6 @@ Item { shadowOffsetX: root.shadowOffsetX shadowOffsetY: root.shadowOffsetY shadowColor: root.shadowColor - blurMax: Theme.elevationBlurMax } Loader { diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 78853995..1d30e13e 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -1237,13 +1237,7 @@ BasePill { fallbackOffset: 6 targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetRadius: Theme.cornerRadius - sourceRect.antialiasing: true - sourceRect.smooth: true - shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled - layer.smooth: true - layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically - layer.samples: 4 + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled } Rectangle { @@ -1689,11 +1683,7 @@ BasePill { fallbackOffset: 6 targetColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) targetRadius: Theme.cornerRadius - sourceRect.antialiasing: true - shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && !BlurService.enabled - layer.smooth: true - layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled } Rectangle { diff --git a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml index 8a4466e8..797ba8b0 100644 --- a/quickshell/Modules/DankDash/MediaDropdownOverlay.qml +++ b/quickshell/Modules/DankDash/MediaDropdownOverlay.qml @@ -130,7 +130,7 @@ Item { borderColor: volumePanel.border.color borderWidth: volumePanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { @@ -272,7 +272,7 @@ Item { borderColor: audioDevicesPanel.border.color borderWidth: audioDevicesPanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { @@ -444,7 +444,7 @@ Item { borderColor: playersPanel.border.color borderWidth: playersPanel.border.width shadowOpacity: Theme.elevationLevel2 && Theme.elevationLevel2.alpha !== undefined ? Theme.elevationLevel2.alpha : 0.25 - shadowEnabled: Theme.elevationEnabled && !BlurService.enabled + shadowEnabled: Theme.elevationEnabled } MouseArea { diff --git a/quickshell/Modules/Frame/FrameBorder.qml b/quickshell/Modules/Frame/FrameBorder.qml index a59eefcf..543654f0 100644 --- a/quickshell/Modules/Frame/FrameBorder.qml +++ b/quickshell/Modules/Frame/FrameBorder.qml @@ -1,13 +1,12 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Shapes import qs.Common // Frame perimeter ring: the full window rectangle with a rounded-rectangle -// cutout. Drawn as a single even-odd Shape so the ring is one primitive: no -// full-output mask textures, and a translucent fill never double-blends at the -// corners. +// cutout, rendered as a signed-distance field with analytic antialiasing. +// One primitive: no full-output mask textures, no corner double-blend, crisp +// edges at any scale without an FBO. Item { id: root @@ -20,42 +19,14 @@ Item { required property real cutoutRadius property color borderColor: Qt.rgba(SettingsData.effectiveFrameColor.r, SettingsData.effectiveFrameColor.g, SettingsData.effectiveFrameColor.b, SettingsData.frameOpacity) - // Path elements can't reference `parent`; expose cutout edges as root props. - readonly property real _left: cutoutLeftInset - readonly property real _top: cutoutTopInset - readonly property real _right: width - cutoutRightInset - readonly property real _bottom: height - cutoutBottomInset - readonly property real _radius: Math.max(0, Math.min(cutoutRadius, (_right - _left) / 2, (_bottom - _top) / 2)) - - Shape { + ShaderEffect { anchors.fill: parent - asynchronous: false - preferredRendererType: Shape.CurveRenderer - antialiasing: true + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/frame_arc.frag.qsb") - ShapePath { - fillColor: root.borderColor - strokeWidth: -1 - fillRule: ShapePath.OddEvenFill - - // Outer rectangle (window edge, square corners) - startX: 0 - startY: 0 - PathLine { x: root.width; y: 0 } - PathLine { x: root.width; y: root.height } - PathLine { x: 0; y: root.height } - PathLine { x: 0; y: 0 } - - // Inner rounded-rectangle cutout (second subpath → even-odd hole) - PathMove { x: root._left + root._radius; y: root._top } - PathLine { x: root._right - root._radius; y: root._top } - PathArc { x: root._right; y: root._top + root._radius; radiusX: root._radius; radiusY: root._radius } - PathLine { x: root._right; y: root._bottom - root._radius } - PathArc { x: root._right - root._radius; y: root._bottom; radiusX: root._radius; radiusY: root._radius } - PathLine { x: root._left + root._radius; y: root._bottom } - PathArc { x: root._left; y: root._bottom - root._radius; radiusX: root._radius; radiusY: root._radius } - PathLine { x: root._left; y: root._top + root._radius } - PathArc { x: root._left + root._radius; y: root._top; radiusX: root._radius; radiusY: root._radius } - } + property real widthPx: width + property real heightPx: height + property real cutoutRadius: root.cutoutRadius + property vector4d cutout: Qt.vector4d(root.cutoutLeftInset, root.cutoutTopInset, root.width - root.cutoutRightInset, root.height - root.cutoutBottomInset) + property vector4d surfaceColor: Qt.vector4d(root.borderColor.r, root.borderColor.g, root.borderColor.b, root.borderColor.a) } } diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index fcf8dd4d..808fcdbb 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -1,12 +1,10 @@ pragma ComponentBehavior: Bound import QtQuick -import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Common import qs.Services -import qs.Widgets import "../../Common/ConnectorGeometry.js" as ConnectorGeometry import "../../Common/ConnectedSurfaceGeometry.js" as SurfaceGeometry @@ -70,11 +68,8 @@ PanelWindow { readonly property real _ccr: Theme.connectedCornerRadius readonly property bool _popoutHorizontal: SurfaceGeometry.isHorizontal(win._popoutDescriptor.barSide) - readonly property bool _notifHorizontal: ConnectorGeometry.isHorizontal(win._notifState.barSide) readonly property bool _modalHorizontal: ConnectorGeometry.isHorizontal(win._modalState.barSide) - readonly property bool _dockHorizontal: ConnectorGeometry.isHorizontal(win._dockState.barSide) readonly property var _popoutBodyGeometry: SurfaceGeometry.animatedBodyRect(win._popoutDescriptor, win._dpr) - readonly property var _popoutStaticBodyGeometry: SurfaceGeometry.bodyRect(win._popoutDescriptor, win._dpr) readonly property var _modalBodyGeometry: SurfaceGeometry.animatedBodyRect(win._modalDescriptor, win._dpr) readonly property var _notifBodyGeometry: SurfaceGeometry.bodyRect(win._notifDescriptor, win._dpr) readonly property var _dockBodyGeometry: SurfaceGeometry.translatedBodyRect(win._dockDescriptor, win._dpr) @@ -97,12 +92,6 @@ PanelWindow { return Math.max(0, Math.min(win._ccr, bodyRadius, maxConnectorRadius)); } - readonly property real _popoutFillOverlapXValue: win._popoutHorizontal ? win._seamOverlap : 0 - readonly property real _popoutFillOverlapYValue: (win._popoutState.barSide === "left" || win._popoutState.barSide === "right") ? win._seamOverlap : 0 - readonly property real _dockFillOverlapXValue: win._dockHorizontal ? win._seamOverlap : 0 - readonly property real _dockFillOverlapYValue: (win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._seamOverlap : 0 - readonly property real _dockJoinOverlapXValue: ConnectorGeometry.isVertical(win._dockState.barSide) ? win._seamOverlap : 0 - readonly property real _dockJoinOverlapYValue: ConnectorGeometry.isHorizontal(win._dockState.barSide) ? win._seamOverlap : 0 readonly property real _notifSideUnderlapValue: ConnectorGeometry.isVertical(win._notifState.barSide) ? win._seamOverlap : 0 readonly property real _notifStartUnderlapValue: win._notifState.omitStartConnector ? win._seamOverlap : 0 readonly property real _notifEndUnderlapValue: win._notifState.omitEndConnector ? win._seamOverlap : 0 @@ -118,7 +107,6 @@ PanelWindow { readonly property real _effectivePopoutFarEndCcr: win._popoutRadii.farEnd readonly property real _effectivePopoutMaxCcr: Math.max(win._effectivePopoutStartCcr, win._effectivePopoutEndCcr) readonly property real _effectivePopoutFarExtent: Math.max(win._effectivePopoutFarStartCcr, win._effectivePopoutFarEndCcr) - readonly property var _popoutChromeGeometry: SurfaceGeometry.chromeBounds(win._popoutStaticBodyGeometry, win._popoutDescriptor.barSide, win._effectivePopoutStartCcr, win._effectivePopoutEndCcr, 0, win._dpr) readonly property var _notifNearRadii: SurfaceGeometry.connectorRadii(win._notifDescriptor, win._notifBodyGeometry, win._ccr, win._surfaceRadius, win._dpr, true) readonly property var _notifFarRadii: SurfaceGeometry.connectorRadii(win._notifDescriptor, win._notifBodyScene(), win._ccr, win._surfaceRadius, win._dpr, true) readonly property real _effectiveNotifCcr: win._notifNearRadii.near @@ -137,20 +125,16 @@ PanelWindow { readonly property real _effectiveModalFarStartCcr: win._modalRadii.farStart readonly property real _effectiveModalFarEndCcr: win._modalRadii.farEnd readonly property real _effectiveModalFarExtent: Math.max(win._effectiveModalFarStartCcr, win._effectiveModalFarEndCcr) - readonly property var _dockChromeGeometry: SurfaceGeometry.chromeBounds(win._dockBodyGeometry, win._dockDescriptor.barSide, win._dockConnectorRadiusValue, win._dockConnectorRadiusValue, 0, win._dpr) readonly property color _surfaceColor: Theme.connectedSurfaceColor - readonly property real _surfaceOpacity: _surfaceColor.a - readonly property color _opaqueSurfaceColor: Qt.rgba(_surfaceColor.r, _surfaceColor.g, _surfaceColor.b, 1) readonly property real _surfaceRadius: Theme.connectedSurfaceRadius readonly property real _seamOverlap: Theme.hairline(win._dpr) readonly property bool _disableLayer: Quickshell.env("DMS_DISABLE_LAYER") === "true" || Quickshell.env("DMS_DISABLE_LAYER") === "1" - // The connected silhouette is drawn directly as one translucent Shape; the - // full-output layer/FBO is allocated only when it must source the elevation - // shadow. Translucency no longer needs a flatten (single primitive). + // Both elevation states render through the SDF shader; this only toggles + // the shadow term inside it. readonly property bool _elevationShadow: win._connectedActive && Theme.elevationEnabled && !win._disableLayer // Active surfaces packed into four fixed SDF-shader slots. Each near (bar) // edge is clamped to the cutout edge so the smooth-min connector attaches - // there; connR (the smin radius) is the connector fillet. + // there; the per-corner smin radius is that corner's junction fillet. readonly property var _sdfSlots: { const T = win.cutoutTopInset; const L = win.cutoutLeftInset; @@ -177,44 +161,54 @@ PanelWindow { if (i < src.length) { const s = src[i]; const b = clampNear(s.side, s.body); - // smin radius = the near connector fillet only; an omitted - // connector contributes no flare (its corner just rounds). - const connR = Math.max(s.radii.startCr, s.radii.endCr); const active = b.width > 0 && b.height > 0 ? 1 : 0; - // A bar-side corner is sharp only where its connector is present; - // an omitted connector (farStart/farEnd set) keeps that corner - // rounded. Far corners always round. (start=left/top, end=right/bottom.) - const bodyR = s.radii.surfaceRadius; + // Map start/end (left/top, right/bottom) onto corners + // (tl,tr,br,bl): bar-side corners take their near connector + // fillet, far corners always take the far fillet so a body + // meeting a perpendicular border joins with an arc (smin is + // inert when nothing is within k). A bar-side corner is sharp + // where its connector is present; an omitted connector makes + // its far corner sharp instead (the far-cap join). + const sc = s.radii.startCr, ec = s.radii.endCr; + // Clamp the far fillet to the body extent so it cannot flare + // back across a shallow body into the bar mid-animation. + const extent = (s.side === "top" || s.side === "bottom") ? b.height : b.width; + const fc = Math.min(s.radii.farCr, extent); const omitS = s.radii.farStartCr > 0; const omitE = s.radii.farEndCr > 0; - let tl = bodyR, tr = bodyR, br = bodyR, bl = bodyR; + const bodyR = s.radii.surfaceRadius; + const nearS = omitS ? bodyR : 0, nearE = omitE ? bodyR : 0; + const farS = omitS ? 0 : bodyR, farE = omitE ? 0 : bodyR; + // An omitted bar-side corner sits flush against the border, so + // it keeps a nonzero fillet (a zero k hard-joins the coincident + // edges and shows a half-coverage hairline along the seam). + const kS = omitS ? fc : sc, kE = omitE ? fc : ec; + let ks, cr; if (s.side === "top") { - if (!omitS) tl = 0; - if (!omitE) tr = 0; + ks = [kS, kE, fc, fc]; + cr = [nearS, nearE, farE, farS]; } else if (s.side === "bottom") { - if (!omitS) bl = 0; - if (!omitE) br = 0; + ks = [fc, fc, kE, kS]; + cr = [farS, farE, nearE, nearS]; } else if (s.side === "left") { - if (!omitS) tl = 0; - if (!omitE) bl = 0; + ks = [kS, fc, fc, kE]; + cr = [nearS, farS, farE, nearE]; } else { - if (!omitS) tr = 0; - if (!omitE) br = 0; + ks = [fc, kS, kE, fc]; + cr = [farS, nearS, nearE, farE]; } out.push({ "rect": Qt.vector4d(b.x, b.y, b.width, b.height), - "corner": Qt.vector4d(tl, tr, br, bl), - "param": Qt.vector4d(connR, active, 0, 0) + "corner": Qt.vector4d(cr[0], cr[1], cr[2], cr[3]), + "k": Qt.vector4d(ks[0], ks[1], ks[2], ks[3]), + "param": Qt.vector4d(active, 0, 0, 0) }); } else { - out.push({"rect": Qt.vector4d(0, 0, 0, 0), "corner": Qt.vector4d(0, 0, 0, 0), "param": Qt.vector4d(0, 0, 0, 0)}); + out.push({"rect": Qt.vector4d(0, 0, 0, 0), "corner": Qt.vector4d(0, 0, 0, 0), "k": Qt.vector4d(0, 0, 0, 0), "param": Qt.vector4d(0, 0, 0, 0)}); } } return out; } - property bool _surfaceRefreshNeedsLayerRecreate: false - property bool _surfaceLayerRecoveryActive: false - function _regionInt(value) { return Math.max(0, Math.round(Theme.px(value, win._dpr))); } @@ -1067,96 +1061,14 @@ PanelWindow { return Math.max(0, Math.min(win._effectiveModalCcr, extent - win._surfaceRadius)); } - function _popoutArcVisible() { - if (!_popoutBodyBlurAnchor._active || _popoutBodyBlurAnchor.width <= 0 || _popoutBodyBlurAnchor.height <= 0) - return false; - return win._popoutArcExtent >= win._ccr * (1 + win._ccr * 0.02); - } - function _popoutBlurCapThickness() { const extent = win._popoutArcExtent; return Math.max(0, Math.min(win._effectivePopoutMaxCcr, extent - win._surfaceRadius)); } - function _popoutChromeX() { - return win._popoutChromeGeometry.x; - } - - function _popoutChromeY() { - return win._popoutChromeGeometry.y; - } - - function _popoutChromeWidth() { - return win._popoutChromeGeometry.width; - } - - function _popoutChromeHeight() { - return win._popoutChromeGeometry.height; - } - - function _popoutClipX() { - return _popoutBodyBlurAnchor.x - win._popoutChromeX() - win._popoutFillOverlapXValue; - } - - function _popoutClipY() { - return _popoutBodyBlurAnchor.y - win._popoutChromeY() - win._popoutFillOverlapYValue; - } - - function _popoutClipWidth() { - return _popoutBodyBlurAnchor.width + win._popoutFillOverlapXValue * 2; - } - - function _popoutClipHeight() { - return _popoutBodyBlurAnchor.height + win._popoutFillOverlapYValue * 2; - } - - function _popoutShapeBodyOffsetX() { - const side = win._popoutState.barSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._effectivePopoutStartCcr; - return side === "right" ? win._effectivePopoutFarExtent : 0; - } - - function _popoutShapeBodyOffsetY() { - const side = win._popoutState.barSide; - if (ConnectorGeometry.isHorizontal(side)) - return side === "bottom" ? win._effectivePopoutFarExtent : 0; - return win._effectivePopoutStartCcr; - } - - function _popoutShapeWidth() { - const side = win._popoutState.barSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._popoutClipWidth() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; - return win._popoutClipWidth() + win._effectivePopoutFarExtent; - } - - function _popoutShapeHeight() { - const side = win._popoutState.barSide; - if (ConnectorGeometry.isHorizontal(side)) - return win._popoutClipHeight() + win._effectivePopoutFarExtent; - return win._popoutClipHeight() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; - } - - function _popoutBodyXInClip() { - return (win._popoutState.barSide === "left" ? _popoutBodyBlurAnchor._dxClamp : 0) - win._popoutFillOverlapXValue; - } - - function _popoutBodyYInClip() { - return (win._popoutState.barSide === "top" ? _popoutBodyBlurAnchor._dyClamp : 0) - win._popoutFillOverlapYValue; - } - - function _popoutBodyFullWidth() { - return win._popoutState.bodyW + win._popoutFillOverlapXValue * 2; - } - - function _popoutBodyFullHeight() { - return win._popoutState.bodyH + win._popoutFillOverlapYValue * 2; - } - // Active connected surfaces fed to the unified silhouette path. Raw animated // body rects (no seam/fill overlap); the builder anchors each to the cutout - // edge. Connector radii match what each ConnectedShape would receive. + // edge. Connector radii use the same per-surface helpers as the blur regions. function _unifiedSurfaces() { const arr = []; const p = win._popoutBodyGeometry; @@ -1165,6 +1077,7 @@ PanelWindow { "side": win._popoutDescriptor.barSide, "body": {"x": p.x, "y": p.y, "width": p.width, "height": p.height}, "radii": { + "farCr": win._effectivePopoutFarCcr, "startCr": win._effectivePopoutStartCcr, "endCr": win._effectivePopoutEndCcr, "farStartCr": win._effectivePopoutFarStartCcr, @@ -1178,6 +1091,7 @@ PanelWindow { "side": win._modalDescriptor.barSide, "body": {"x": m.x, "y": m.y, "width": m.width, "height": m.height}, "radii": { + "farCr": win._effectiveModalFarCcr, "startCr": win._effectiveModalStartCcr, "endCr": win._effectiveModalEndCcr, "farStartCr": win._effectiveModalFarStartCcr, @@ -1191,6 +1105,7 @@ PanelWindow { "side": win._notifDescriptor.barSide, "body": {"x": n.x, "y": n.y, "width": n.width, "height": n.height}, "radii": { + "farCr": win._effectiveNotifFarCcr, "startCr": win._effectiveNotifStartCcr, "endCr": win._effectiveNotifEndCcr, "farStartCr": win._effectiveNotifFarStartCcr, @@ -1204,6 +1119,7 @@ PanelWindow { "side": win._dockDescriptor.barSide, "body": {"x": dk.x, "y": dk.y, "width": dk.width, "height": dk.height}, "radii": { + "farCr": win._dockConnectorRadiusValue, "startCr": win._dockConnectorRadiusValue, "endCr": win._dockConnectorRadiusValue, "farStartCr": 0, @@ -1214,38 +1130,6 @@ PanelWindow { return arr; } - function _dockChromeX() { - return win._dockChromeGeometry.x; - } - - function _dockChromeY() { - return win._dockChromeGeometry.y; - } - - function _dockChromeWidth() { - return win._dockChromeGeometry.width; - } - - function _dockChromeHeight() { - return win._dockChromeGeometry.height; - } - - function _dockBodyXInChrome() { - return (ConnectorGeometry.isHorizontal(win._dockState.barSide) ? win._dockConnectorRadiusValue : 0) - win._dockFillOverlapXValue; - } - - function _dockBodyYInChrome() { - return ((win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._dockConnectorRadiusValue : 0) - win._dockFillOverlapYValue; - } - - function _dockJoinOverlapXOffset() { - return win._dockState.barSide === "left" ? -win._dockJoinOverlapXValue : 0; - } - - function _dockJoinOverlapYOffset() { - return win._dockState.barSide === "top" ? -win._dockJoinOverlapYValue : 0; - } - function _farConnectorBarSide(sourceSide, placement) { if (sourceSide === "top" || sourceSide === "bottom") return placement === "left" ? "left" : "right"; @@ -1317,28 +1201,13 @@ PanelWindow { } catch (e) {} } - function _scheduleSurfaceRefresh(recreateLayer) { - if (recreateLayer) - _surfaceRefreshNeedsLayerRecreate = true; + function _scheduleSurfaceRefresh() { surfaceRefreshAction.restart(); } function _runSurfaceRefresh() { if (!win.visible) return; - if (_surfaceRefreshNeedsLayerRecreate) { - _surfaceRefreshNeedsLayerRecreate = false; - if (win._elevationShadow) { - _surfaceLayerRecoveryActive = true; - surfaceLayerRestoreAction.restart(); - } - } - _requestContentUpdate(); - _republishFrameBlur(); - } - - function _finishSurfaceLayerRecovery() { - _surfaceLayerRecoveryActive = false; _requestContentUpdate(); _republishFrameBlur(); } @@ -1348,11 +1217,6 @@ PanelWindow { onTriggered: win._runSurfaceRefresh() } - DeferredAction { - id: surfaceLayerRestoreAction - onTriggered: win._finishSurfaceLayerRecovery() - } - Connections { target: SettingsData function onFrameBlurEnabledChanged() { @@ -1397,41 +1261,33 @@ PanelWindow { onVisibleChanged: { if (visible) { win._scheduleBlurRebuild(); - win._scheduleSurfaceRefresh(false); + win._scheduleSurfaceRefresh(); } else { surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); - _surfaceLayerRecoveryActive = false; - _surfaceRefreshNeedsLayerRecreate = false; _teardownBlur(); } } - on_SurfaceRevisionChanged: win._scheduleSurfaceRefresh(false) + on_SurfaceRevisionChanged: win._scheduleSurfaceRefresh() onResourcesLost: { blurRebuildAction.cancel(); surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); - _surfaceRefreshNeedsLayerRecreate = true; - if (win._elevationShadow) - _surfaceLayerRecoveryActive = true; win._teardownBlur(); } onWindowConnected: { - win._scheduleSurfaceRefresh(true); + win._scheduleSurfaceRefresh(); win._scheduleBlurRebuild(); } Component.onCompleted: { win._scheduleBlurRebuild(); - win._scheduleSurfaceRefresh(false); + win._scheduleSurfaceRefresh(); } Component.onDestruction: { blurRebuildAction.cancel(); surfaceRefreshAction.cancel(); - surfaceLayerRestoreAction.cancel(); win._teardownBlur(); } @@ -1445,258 +1301,41 @@ PanelWindow { cutoutRadius: win.cutoutRadius } - Item { - id: _connectedSurfaceLayer + // The entire connected silhouette (frame ring + every active chrome) as one + // SDF in a fragment shader. Analytic fwidth AA → crisp at any scale, no FBO; + // the smooth-min radius is the connector. The elevation shadow is derived + // from the same distance field, so elevation-on needs no grouping layer. + ShaderEffect { anchors.fill: parent visible: win._connectedActive - // Elevation-off draws the silhouette directly at the surface color, so - // group opacity is 1. Elevation-on draws children opaque and applies the - // surface alpha to the whole group beneath the shadow. - opacity: win._elevationShadow ? win._surfaceOpacity : 1 - // FBO is allocated only to source the elevation shadow. - layer.enabled: win._elevationShadow && !win._surfaceLayerRecoveryActive - layer.smooth: false + fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_arc.frag.qsb") - layer.effect: MultiEffect { - readonly property var level: Theme.elevationLevel2 - readonly property real _shadowBlur: Theme.elevationEnabled ? (level && level.blurPx !== undefined ? level.blurPx : 0) : 0 - readonly property real _shadowSpread: Theme.elevationEnabled ? (level && level.spreadPx !== undefined ? level.spreadPx : 0) : 0 - - autoPaddingEnabled: true - blurEnabled: false - maskEnabled: false - - shadowEnabled: !win._disableLayer && Theme.elevationEnabled - shadowBlur: Math.max(0, Math.min(1, _shadowBlur / Math.max(1, Theme.elevationBlurMax))) - shadowScale: 1 + (2 * _shadowSpread) / Math.max(1, Math.min(_connectedSurfaceLayer.width, _connectedSurfaceLayer.height)) - shadowHorizontalOffset: Theme.elevationOffsetXFor(level, Theme.elevationLightDirection, 4) - shadowVerticalOffset: Theme.elevationOffsetYFor(level, Theme.elevationLightDirection, 4) - shadowColor: Theme.elevationShadowColor(level) - shadowOpacity: 1 - } - - // Elevation-off: the entire connected silhouette (frame ring + every - // active chrome) as one SDF in a fragment shader. Analytic fwidth AA → - // crisp at any scale, no FBO; the smooth-min radius is the connector. - ShaderEffect { - anchors.fill: parent - visible: win._connectedActive && !win._elevationShadow - fragmentShader: Qt.resolvedUrl("../../Shaders/qsb/connected_arc.frag.qsb") - - property real widthPx: width - property real heightPx: height - property real cutoutRadius: win.cutoutRadius - property vector4d cutout: Qt.vector4d(win.cutoutLeftInset, win.cutoutTopInset, win.width - win.cutoutRightInset, win.height - win.cutoutBottomInset) - property vector4d surfaceColor: Qt.vector4d(win._surfaceColor.r, win._surfaceColor.g, win._surfaceColor.b, win._surfaceColor.a) - property vector4d chromeRect0: win._sdfSlots[0].rect - property vector4d chromeCorner0: win._sdfSlots[0].corner - property vector4d chromeParam0: win._sdfSlots[0].param - property vector4d chromeRect1: win._sdfSlots[1].rect - property vector4d chromeCorner1: win._sdfSlots[1].corner - property vector4d chromeParam1: win._sdfSlots[1].param - property vector4d chromeRect2: win._sdfSlots[2].rect - property vector4d chromeCorner2: win._sdfSlots[2].corner - property vector4d chromeParam2: win._sdfSlots[2].param - property vector4d chromeRect3: win._sdfSlots[3].rect - property vector4d chromeCorner3: win._sdfSlots[3].corner - property vector4d chromeParam3: win._sdfSlots[3].param - } - - // Elevation-on: opaque children flattened in the FBO so the MultiEffect - // can derive one shadow; the layer applies the surface alpha. - FrameBorder { - anchors.fill: parent - visible: win._elevationShadow - borderColor: win._opaqueSurfaceColor - cutoutTopInset: win.cutoutTopInset - cutoutBottomInset: win.cutoutBottomInset - cutoutLeftInset: win.cutoutLeftInset - cutoutRightInset: win.cutoutRightInset - cutoutRadius: win.cutoutRadius - } - - Item { - id: _connectedChrome - anchors.fill: parent - visible: win._elevationShadow - - Item { - id: _popoutChrome - visible: win._popoutState.visible && win._popoutState.screen === win._screenName - x: win._popoutChromeX() - y: win._popoutChromeY() - width: win._popoutChromeWidth() - height: win._popoutChromeHeight() - - Item { - id: _popoutClip - x: win._popoutClipX() - win._popoutShapeBodyOffsetX() - y: win._popoutClipY() - win._popoutShapeBodyOffsetY() - width: win._popoutShapeWidth() - height: win._popoutShapeHeight() - clip: true - - ConnectedShape { - id: _popoutShape - visible: _popoutBodyBlurAnchor._active && _popoutBodyBlurAnchor.width > 0 && _popoutBodyBlurAnchor.height > 0 - barSide: win._popoutState.barSide - bodyWidth: win._popoutClipWidth() - bodyHeight: win._popoutClipHeight() - connectorRadius: win._effectivePopoutCcr - startConnectorRadius: win._effectivePopoutStartCcr - endConnectorRadius: win._effectivePopoutEndCcr - farStartConnectorRadius: win._effectivePopoutFarStartCcr - farEndConnectorRadius: win._effectivePopoutFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - } - - Item { - id: _dockChrome - visible: _dockBodyBlurAnchor._active - x: win._dockChromeX() - y: win._dockChromeY() - width: win._dockChromeWidth() - height: win._dockChromeHeight() - - Rectangle { - id: _dockFill - x: win._dockBodyXInChrome() + win._dockJoinOverlapXOffset() - y: win._dockBodyYInChrome() + win._dockJoinOverlapYOffset() - width: _dockBodyBlurAnchor.width + win._dockFillOverlapXValue * 2 + win._dockJoinOverlapXValue - height: _dockBodyBlurAnchor.height + win._dockFillOverlapYValue * 2 + win._dockJoinOverlapYValue - color: win._opaqueSurfaceColor - z: 1 - - readonly property string _dockSide: win._dockState.barSide - readonly property real _dockRadius: win._dockBodyBlurRadiusValue - topLeftRadius: (_dockSide === "top" || _dockSide === "left") ? 0 : _dockRadius - topRightRadius: (_dockSide === "top" || _dockSide === "right") ? 0 : _dockRadius - bottomLeftRadius: (_dockSide === "bottom" || _dockSide === "left") ? 0 : _dockRadius - bottomRightRadius: (_dockSide === "bottom" || _dockSide === "right") ? 0 : _dockRadius - } - - ConnectedCorner { - id: _connDockLeft - visible: _dockBodyBlurAnchor._active - barSide: win._dockState.barSide - placement: "left" - spacing: 0 - connectorRadius: win._dockConnectorRadiusValue - color: win._opaqueSurfaceColor - dpr: win._dpr - x: Theme.snap(_dockLeftConnectorBlurAnchor.x - _dockChrome.x, win._dpr) - y: Theme.snap(_dockLeftConnectorBlurAnchor.y - _dockChrome.y, win._dpr) - } - - ConnectedCorner { - id: _connDockRight - visible: _dockBodyBlurAnchor._active - barSide: win._dockState.barSide - placement: "right" - spacing: 0 - connectorRadius: win._dockConnectorRadiusValue - color: win._opaqueSurfaceColor - dpr: win._dpr - x: Theme.snap(_dockRightConnectorBlurAnchor.x - _dockChrome.x, win._dpr) - y: Theme.snap(_dockRightConnectorBlurAnchor.y - _dockChrome.y, win._dpr) - } - } - } - - Item { - id: _notifChrome - visible: win._elevationShadow && _notifBodySceneBlurAnchor._active - - readonly property string _notifSide: win._notifState.barSide - readonly property bool _isHoriz: _notifSide === "top" || _notifSide === "bottom" - readonly property real _startCcr: win._effectiveNotifStartCcr - readonly property real _endCcr: win._effectiveNotifEndCcr - readonly property real _farExtent: win._effectiveNotifFarExtent - readonly property real _bodyW: Theme.snap(_notifBodySceneBlurAnchor.width, win._dpr) - readonly property real _bodyH: Theme.snap(_notifBodySceneBlurAnchor.height, win._dpr) - readonly property var _geometry: SurfaceGeometry.chromeBounds(_notifBodySceneBlurAnchor, _notifSide, _startCcr, _endCcr, _farExtent, win._dpr) - - z: _isHoriz ? 0 : -1 - x: _geometry.x - y: _geometry.y - width: _geometry.width - height: _geometry.height - - ConnectedShape { - visible: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 - barSide: _notifChrome._notifSide - bodyWidth: _notifChrome._bodyW - bodyHeight: _notifChrome._bodyH - connectorRadius: win._effectiveNotifCcr - startConnectorRadius: _notifChrome._startCcr - endConnectorRadius: _notifChrome._endCcr - farStartConnectorRadius: win._effectiveNotifFarStartCcr - farEndConnectorRadius: win._effectiveNotifFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - - // Bar-side-bounded clip so modal chrome retracts behind the bar on exit - // instead of sliding over bar widgets (mirrors the popout `_popoutClip`). - Item { - id: _modalClip - visible: win._elevationShadow && _modalBodyBlurAnchor._active - z: 1 - - readonly property string _modalSide: win._modalState.barSide - readonly property real _inset: _modalBodyBlurAnchor._active && win.screen ? SettingsData.frameEdgeInsetForSide(win.screen, _modalSide) : 0 - readonly property real _topBound: _modalSide === "top" ? _inset : 0 - readonly property real _bottomBound: _modalSide === "bottom" ? (win.height - _inset) : win.height - readonly property real _leftBound: _modalSide === "left" ? _inset : 0 - readonly property real _rightBound: _modalSide === "right" ? (win.width - _inset) : win.width - - x: _leftBound - y: _topBound - width: Math.max(0, _rightBound - _leftBound) - height: Math.max(0, _bottomBound - _topBound) - clip: true - - Item { - id: _modalChrome - - readonly property string _modalSide: win._modalState.barSide - readonly property bool _isHoriz: _modalSide === "top" || _modalSide === "bottom" - readonly property real _startCcr: win._effectiveModalStartCcr - readonly property real _endCcr: win._effectiveModalEndCcr - readonly property real _farExtent: win._effectiveModalFarExtent - readonly property real _bodyW: Theme.snap(_modalBodyBlurAnchor.width, win._dpr) - readonly property real _bodyH: Theme.snap(_modalBodyBlurAnchor.height, win._dpr) - readonly property var _geometry: SurfaceGeometry.chromeBounds(_modalBodyBlurAnchor, _modalSide, _startCcr, _endCcr, _farExtent, win._dpr) - - x: Theme.snap(_geometry.x - _modalClip.x, win._dpr) - y: Theme.snap(_geometry.y - _modalClip.y, win._dpr) - width: _geometry.width - height: _geometry.height - - ConnectedShape { - visible: _modalBodyBlurAnchor._active && _modalChrome._bodyW > 0 && _modalChrome._bodyH > 0 - barSide: _modalChrome._modalSide - bodyWidth: _modalChrome._bodyW - bodyHeight: _modalChrome._bodyH - connectorRadius: win._effectiveModalCcr - startConnectorRadius: _modalChrome._startCcr - endConnectorRadius: _modalChrome._endCcr - farStartConnectorRadius: win._effectiveModalFarStartCcr - farEndConnectorRadius: win._effectiveModalFarEndCcr - surfaceRadius: win._surfaceRadius - fillColor: win._opaqueSurfaceColor - x: 0 - y: 0 - } - } - } + readonly property var _level: Theme.elevationLevel2 + readonly property color _shadowTint: Theme.elevationShadowColor(_level) + readonly property var _ambient: Theme.elevationAmbient(_level) + property real widthPx: width + property real heightPx: height + property real cutoutRadius: win.cutoutRadius + property vector4d cutout: Qt.vector4d(win.cutoutLeftInset, win.cutoutTopInset, win.width - win.cutoutRightInset, win.height - win.cutoutBottomInset) + property vector4d surfaceColor: Qt.vector4d(win._surfaceColor.r, win._surfaceColor.g, win._surfaceColor.b, win._surfaceColor.a) + property vector4d shadowColor: Qt.vector4d(_shadowTint.r, _shadowTint.g, _shadowTint.b, win._elevationShadow ? _shadowTint.a : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, _level.blurPx), Math.max(0, _level.spreadPx), Theme.elevationOffsetXFor(_level, Theme.elevationLightDirection, 4), Theme.elevationOffsetYFor(_level, Theme.elevationLightDirection, 4)) + property vector4d ambientParam: Qt.vector4d(_ambient.blurPx, _ambient.spreadPx, win._elevationShadow ? _ambient.alpha : 0, 0) + property vector4d chromeRect0: win._sdfSlots[0].rect + property vector4d chromeCorner0: win._sdfSlots[0].corner + property vector4d chromeK0: win._sdfSlots[0].k + property vector4d chromeParam0: win._sdfSlots[0].param + property vector4d chromeRect1: win._sdfSlots[1].rect + property vector4d chromeCorner1: win._sdfSlots[1].corner + property vector4d chromeK1: win._sdfSlots[1].k + property vector4d chromeParam1: win._sdfSlots[1].param + property vector4d chromeRect2: win._sdfSlots[2].rect + property vector4d chromeCorner2: win._sdfSlots[2].corner + property vector4d chromeK2: win._sdfSlots[2].k + property vector4d chromeParam2: win._sdfSlots[2].param + property vector4d chromeRect3: win._sdfSlots[3].rect + property vector4d chromeCorner3: win._sdfSlots[3].corner + property vector4d chromeK3: win._sdfSlots[3].k + property vector4d chromeParam3: win._sdfSlots[3].param } } diff --git a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml index be55f2e8..7b7cf43d 100644 --- a/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/HistoryNotificationCard.qml @@ -31,7 +31,7 @@ Rectangle { height: baseCardHeight + contentItem.extraHeight radius: Theme.cornerRadius clip: false - readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled + readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" ElevationShadow { id: shadowLayer diff --git a/quickshell/Modules/Notifications/Center/NotificationCard.qml b/quickshell/Modules/Notifications/Center/NotificationCard.qml index 22a3e285..9c8869ec 100644 --- a/quickshell/Modules/Notifications/Center/NotificationCard.qml +++ b/quickshell/Modules/Notifications/Center/NotificationCard.qml @@ -47,7 +47,7 @@ Rectangle { readonly property real targetHeight: expanded ? (expandedContent.height + cardPadding * 2) : (baseCardHeight + collapsedContent.extraHeight) radius: connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius scale: (cardHoverHandler.hovered ? 1.004 : 1.0) * listLevelAdjacentScaleInfluence - readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !BlurService.enabled + readonly property bool shadowsAllowed: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" readonly property var shadowElevation: Theme.elevationLevel1 readonly property real baseShadowBlurPx: (shadowElevation && shadowElevation.blurPx !== undefined) ? shadowElevation.blurPx : 4 readonly property real hoverShadowBlurBoost: cardHoverHandler.hovered ? Math.min(2, baseShadowBlurPx * 0.25) : 0 diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 63449d13..0755a93c 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -641,21 +641,15 @@ PanelWindow { shadowOffsetY: content.shadowOffsetY shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent" shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode - layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) - layer.textureMirroring: ShaderEffectSource.MirrorVertically - sourceRect.anchors.fill: undefined - sourceRect.x: content.shadowRenderPadding + content.cardInset - sourceRect.y: content.shadowRenderPadding + content.cardInset - sourceRect.width: Math.max(0, content.width - (content.cardInset * 2)) - sourceRect.height: Math.max(0, content.height - (content.cardInset * 2)) - sourceRect.radius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius - sourceRect.color: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface - sourceRect.antialiasing: true - sourceRect.layer.enabled: false - sourceRect.layer.textureSize: Qt.size(0, 0) - sourceRect.border.color: notificationData && notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) - sourceRect.border.width: notificationData && notificationData.urgency === NotificationUrgency.Critical ? 2 : 0 + sourceX: content.shadowRenderPadding + content.cardInset + sourceY: content.shadowRenderPadding + content.cardInset + sourceWidth: Math.max(0, content.width - (content.cardInset * 2)) + sourceHeight: Math.max(0, content.height - (content.cardInset * 2)) + targetRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius + targetColor: win.connectedFrameMode ? Theme.floatingSurface : Theme.readableSurface + borderColor: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? Theme.withAlpha(Theme.primary, 0.3) : Theme.withAlpha(Theme.outline, 0.08) + borderWidth: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical ? 2 : 0 } // Keep critical accent outside shadow rendering so connected mode still shows it. diff --git a/quickshell/Shaders/frag/connected_arc.frag b/quickshell/Shaders/frag/connected_arc.frag index c19a8d0a..04c11586 100644 --- a/quickshell/Shaders/frag/connected_arc.frag +++ b/quickshell/Shaders/frag/connected_arc.frag @@ -4,6 +4,8 @@ // (an inverted rounded rectangle) smooth-unioned with each active chrome // (popout/modal, dock, notification). The smooth-min radius IS the connector // fillet. Antialiasing is analytic via fwidth -> crisp at any scale, no FBO. +// The elevation shadow samples the same field at the light offset, so both +// elevation states render in this one pass. layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; @@ -16,19 +18,27 @@ layout(std140, binding = 0) uniform buf { float cutoutRadius; vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom vec4 surfaceColor; // straight (non-premultiplied) rgba - // Up to four chrome slots. rect = x,y,w,h (px). corner = per-corner radii - // (topLeft, topRight, bottomRight, bottomLeft). param = connectorR, active, 0, 0 + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha + // Up to four chrome slots. rect = x,y,w,h (px). corner = per-corner radii, + // k = per-corner junction fillet radii (both topLeft, topRight, bottomRight, + // bottomLeft; a corner is sharp exactly where its k > 0). param = active, 0, 0, 0 vec4 chromeRect0; vec4 chromeCorner0; + vec4 chromeK0; vec4 chromeParam0; vec4 chromeRect1; vec4 chromeCorner1; + vec4 chromeK1; vec4 chromeParam1; vec4 chromeRect2; vec4 chromeCorner2; + vec4 chromeK2; vec4 chromeParam2; vec4 chromeRect3; vec4 chromeCorner3; + vec4 chromeK3; vec4 chromeParam3; } ubuf; @@ -64,9 +74,13 @@ float chromeDist(vec2 px, vec4 rect, vec4 corner) { return sdRoundBox4(px, c, rect.zw * 0.5, corner); } -void main() { - vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); +// Per-corner junction fillet radius, selected by chrome-rect quadrant. +float chromeK(vec2 px, vec4 rect, vec4 ks) { + vec2 p = px - (rect.xy + rect.zw * 0.5); + return (p.x >= 0.0) ? (p.y >= 0.0 ? ks.z : ks.y) : (p.y >= 0.0 ? ks.w : ks.x); +} +float sceneDist(vec2 px) { // Frame ring: inside the screen rect AND outside the rounded cutout (hole). vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5; float dOuter = sdBox(px, sc, sc); @@ -75,18 +89,35 @@ void main() { float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius); float d = max(dOuter, -dCut); - // Smooth-union the active chrome surfaces; smin radius = connector fillet. - if (ubuf.chromeParam0.y > 0.5) - d = smin(d, chromeDist(px, ubuf.chromeRect0, ubuf.chromeCorner0), ubuf.chromeParam0.x); - if (ubuf.chromeParam1.y > 0.5) - d = smin(d, chromeDist(px, ubuf.chromeRect1, ubuf.chromeCorner1), ubuf.chromeParam1.x); - if (ubuf.chromeParam2.y > 0.5) - d = smin(d, chromeDist(px, ubuf.chromeRect2, ubuf.chromeCorner2), ubuf.chromeParam2.x); - if (ubuf.chromeParam3.y > 0.5) - d = smin(d, chromeDist(px, ubuf.chromeRect3, ubuf.chromeCorner3), ubuf.chromeParam3.x); + // Smooth-union the active chrome surfaces; smin radius = junction fillet. + if (ubuf.chromeParam0.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect0, ubuf.chromeCorner0), chromeK(px, ubuf.chromeRect0, ubuf.chromeK0)); + if (ubuf.chromeParam1.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect1, ubuf.chromeCorner1), chromeK(px, ubuf.chromeRect1, ubuf.chromeK1)); + if (ubuf.chromeParam2.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect2, ubuf.chromeCorner2), chromeK(px, ubuf.chromeRect2, ubuf.chromeK2)); + if (ubuf.chromeParam3.x > 0.5) + d = smin(d, chromeDist(px, ubuf.chromeRect3, ubuf.chromeCorner3), chromeK(px, ubuf.chromeRect3, ubuf.chromeK3)); + return d; +} +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = sceneDist(px); float fw = max(fwidth(d), 1e-4); float cov = 1.0 - smoothstep(-fw, fw, d); - float a = ubuf.surfaceColor.a * cov * ubuf.qt_Opacity; - fragColor = vec4(ubuf.surfaceColor.rgb * a, a); + // Opaque silhouette over shadow, then the surface alpha applied to the + // whole result — matches the old flattened-FBO + group-opacity look. + vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov; + if (ubuf.shadowColor.a > 0.0) { + float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + // Ambient wrap reuses the field already computed for the silhouette. + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a)); + } + fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity); } diff --git a/quickshell/Shaders/frag/connected_chrome.frag b/quickshell/Shaders/frag/connected_chrome.frag new file mode 100644 index 00000000..bc218460 --- /dev/null +++ b/quickshell/Shaders/frag/connected_chrome.frag @@ -0,0 +1,71 @@ +#version 450 + +// Popout-local connected chrome as a signed-distance field: the body rounded +// rect smooth-unioned against the bar-edge half-plane, so the connector +// fillets form analytically — the SDF twin of the old ConnectedShape + +// ConnectedCorner + MultiEffect FBO. Key + ambient shadows sample the same +// field; shadow is masked outside the silhouette. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + vec4 surfaceColor; // straight (non-premultiplied) rgba + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha + vec4 bodyRect; // body rounded rect in item px: x, y, w, h + vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft + vec4 edgeParam; // x = bar side (0 top, 1 bottom, 2 left, 3 right), y = fillet k +} ubuf; + +// Per-corner rounded box. r = (topLeft, topRight, bottomRight, bottomLeft). +float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) { + p -= c; + float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x); + rr = min(rr, min(hs.x, hs.y)); + vec2 q = abs(p) - hs + rr; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +// Circular smooth-min: blends two SDFs with a fillet of radius k. +float smin(float a, float b, float k) { + if (k <= 0.0) + return min(a, b); + return max(k, min(a, b)) - length(max(vec2(k) - vec2(a, b), vec2(0.0))); +} + +float sceneDist(vec2 px) { + // Bar edge as a half-plane whose interior lies off-item, on the bar's + // side; only its fillet contribution is visible. + float side = ubuf.edgeParam.x; + float dEdge = side < 0.5 ? px.y + : side < 1.5 ? (ubuf.heightPx - px.y) + : side < 2.5 ? px.x + : (ubuf.widthPx - px.x); + vec2 hs = ubuf.bodyRect.zw * 0.5; + float dBody = sdRoundBox4(px, ubuf.bodyRect.xy + hs, hs, ubuf.cornerRadius); + return smin(dEdge, dBody, ubuf.edgeParam.y); +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = sceneDist(px); + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + vec4 col = vec4(ubuf.surfaceColor.rgb, 1.0) * cov; + if (ubuf.shadowColor.a > 0.0) { + float dk = sceneDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - col.a)); + } + fragColor = col * (ubuf.surfaceColor.a * ubuf.qt_Opacity); +} diff --git a/quickshell/Shaders/frag/elevation_rect.frag b/quickshell/Shaders/frag/elevation_rect.frag new file mode 100644 index 00000000..883c7861 --- /dev/null +++ b/quickshell/Shaders/frag/elevation_rect.frag @@ -0,0 +1,60 @@ +#version 450 + +// Standalone elevation surface as a signed-distance field: one quad draws the +// rounded-rect fill, its border, and the M3 two-part shadow (directional key + +// non-directional ambient) analytically — no FBO, no blur passes. The shadow +// is masked to outside the silhouette, so translucent fills never get +// interior darkening. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + float borderWidth; + vec4 rectPx; // rounded rect in item px: x, y, w, h + vec4 cornerRadius; // topLeft, topRight, bottomRight, bottomLeft + vec4 fillColor; // straight (non-premultiplied) rgba + vec4 borderColor; // straight rgba + vec4 shadowColor; // straight rgba; a = 0 disables both shadow terms + vec4 shadowParam; // key: x = blur px, y = spread px, z,w = offset px + vec4 ambientParam; // ambient: x = blur px, y = spread px, z = alpha +} ubuf; + +// Per-corner rounded box. r = (topLeft, topRight, bottomRight, bottomLeft). +float sdRoundBox4(vec2 p, vec2 c, vec2 hs, vec4 r) { + p -= c; + float rr = (p.x >= 0.0) ? (p.y >= 0.0 ? r.z : r.y) : (p.y >= 0.0 ? r.w : r.x); + rr = min(rr, min(hs.x, hs.y)); + vec2 q = abs(p) - hs + rr; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - rr; +} + +float rectDist(vec2 px) { + vec2 hs = ubuf.rectPx.zw * 0.5; + return sdRoundBox4(px, ubuf.rectPx.xy + hs, hs, ubuf.cornerRadius); +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + float d = rectDist(px); + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + // Qt Rectangle semantics: border band on the rim, fill inset inside it. + float covInner = 1.0 - smoothstep(-fw, fw, d + ubuf.borderWidth); + vec4 col = vec4(ubuf.fillColor.rgb, 1.0) * (ubuf.fillColor.a * covInner) + + vec4(ubuf.borderColor.rgb, 1.0) * (ubuf.borderColor.a * max(0.0, cov - covInner)); + if (ubuf.shadowColor.a > 0.0) { + float dk = rectDist(px - ubuf.shadowParam.zw) - ubuf.shadowParam.y; + float bk = max(ubuf.shadowParam.x, fw); + float covK = 1.0 - smoothstep(-bk, bk, dk); + float ba = max(ubuf.ambientParam.x, fw); + float covA = 1.0 - smoothstep(-ba, ba, d - ubuf.ambientParam.y); + float sh = 1.0 - (1.0 - covK * ubuf.shadowColor.a) * (1.0 - covA * ubuf.ambientParam.z); + col += vec4(ubuf.shadowColor.rgb, 1.0) * (sh * (1.0 - cov)); + } + fragColor = col * ubuf.qt_Opacity; +} diff --git a/quickshell/Shaders/frag/frame_arc.frag b/quickshell/Shaders/frag/frame_arc.frag new file mode 100644 index 00000000..d7783b2f --- /dev/null +++ b/quickshell/Shaders/frag/frame_arc.frag @@ -0,0 +1,44 @@ +#version 450 + +// Frame perimeter ring as a signed-distance field: the window rectangle minus +// a rounded-rectangle cutout. Antialiasing is analytic via fwidth -> crisp at +// any scale, no FBO and no mask textures. + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float widthPx; + float heightPx; + float cutoutRadius; + vec4 cutout; // inner cutout edges in px: x=left y=top z=right w=bottom + vec4 surfaceColor; // straight (non-premultiplied) rgba +} ubuf; + +float sdBox(vec2 p, vec2 c, vec2 hs) { + vec2 q = abs(p - c) - hs; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))); +} + +float sdRoundBox(vec2 p, vec2 c, vec2 hs, float r) { + r = min(r, min(hs.x, hs.y)); + vec2 q = abs(p - c) - hs + r; + return min(max(q.x, q.y), 0.0) + length(max(q, vec2(0.0))) - r; +} + +void main() { + vec2 px = qt_TexCoord0 * vec2(ubuf.widthPx, ubuf.heightPx); + vec2 sc = vec2(ubuf.widthPx, ubuf.heightPx) * 0.5; + float dOuter = sdBox(px, sc, sc); + vec2 cutC = vec2((ubuf.cutout.x + ubuf.cutout.z) * 0.5, (ubuf.cutout.y + ubuf.cutout.w) * 0.5); + vec2 cutH = vec2((ubuf.cutout.z - ubuf.cutout.x) * 0.5, (ubuf.cutout.w - ubuf.cutout.y) * 0.5); + float dCut = sdRoundBox(px, cutC, cutH, ubuf.cutoutRadius); + float d = max(dOuter, -dCut); + + float fw = max(fwidth(d), 1e-4); + float cov = 1.0 - smoothstep(-fw, fw, d); + float a = ubuf.surfaceColor.a * cov * ubuf.qt_Opacity; + fragColor = vec4(ubuf.surfaceColor.rgb * a, a); +} diff --git a/quickshell/Shaders/qsb/connected_arc.frag.qsb b/quickshell/Shaders/qsb/connected_arc.frag.qsb index 5e15a97df428dcc4b9d0939259d92b9e159b7a10..ee72cdf41242b5af5056f96190a47b6cbddfc402 100644 GIT binary patch literal 7880 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 HgnJ&d_s{F8 literal 0 HcmV?d00001 diff --git a/quickshell/Shaders/qsb/elevation_rect.frag.qsb b/quickshell/Shaders/qsb/elevation_rect.frag.qsb new file mode 100644 index 0000000000000000000000000000000000000000..01ddc47ce4a8d47e95f5c94aabd42dafbcf56427 GIT binary patch literal 4215 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 minBound && value - minBound < snapDist) + return minBound; + return value; + } + function _closeGapClampedToFrameSide(side) { if (!root.closeFrameGapsActive) return false; @@ -714,9 +727,11 @@ Item { return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth)); default: const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2); - const minX = Math.max(edgeGapLeft, adjacentBarClearance(adjacentBarInfo.leftBar)); - const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, adjacentBarClearance(adjacentBarInfo.rightBar)); - return Math.max(minX, Math.min(maxX, rawX)); + const clearLeft = adjacentBarClearance(adjacentBarInfo.leftBar); + const clearRight = adjacentBarClearance(adjacentBarInfo.rightBar); + const minX = Math.max(edgeGapLeft, clearLeft); + const maxX = screenWidth - popupWidth - Math.max(edgeGapRight, clearRight); + return _snapNearFrameBound(Math.max(minX, Math.min(maxX, rawX)), minX, maxX, edgeGapLeft >= clearLeft, edgeGapRight >= clearRight); } })(), dpr) @@ -735,9 +750,11 @@ Item { return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY)); default: const rawY = triggerY - (popupHeight / 2); - const minY = Math.max(edgeGapTop, adjacentBarClearance(adjacentBarInfo.topBar)); - const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, adjacentBarClearance(adjacentBarInfo.bottomBar)); - return Math.max(minY, Math.min(maxY, rawY)); + const clearTop = adjacentBarClearance(adjacentBarInfo.topBar); + const clearBottom = adjacentBarClearance(adjacentBarInfo.bottomBar); + const minY = Math.max(edgeGapTop, clearTop); + const maxY = screenHeight - popupHeight - Math.max(edgeGapBottom, clearBottom); + return _snapNearFrameBound(Math.max(minY, Math.min(maxY, rawY)), minY, maxY, edgeGapTop >= clearTop, edgeGapBottom >= clearBottom); } })(), dpr) @@ -1024,22 +1041,13 @@ Item { ElevationShadow { id: shadowSource - readonly property real connectorExtent: root.usesConnectedSurfaceChrome ? Theme.connectedCornerRadius : 0 - readonly property real extraLeft: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraRight: root.usesConnectedSurfaceChrome && (contentContainer.barTop || contentContainer.barBottom) ? connectorExtent : 0 - readonly property real extraTop: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 - readonly property real extraBottom: root.usesConnectedSurfaceChrome && (contentContainer.barLeft || contentContainer.barRight) ? connectorExtent : 0 - readonly property real bodyX: extraLeft - readonly property real bodyY: extraTop - readonly property real bodyWidth: rollOutAdjuster.baseWidth - readonly property real bodyHeight: rollOutAdjuster.baseHeight - - width: rollOutAdjuster.baseWidth + extraLeft + extraRight - height: rollOutAdjuster.baseHeight + extraTop + extraBottom + visible: !root.usesConnectedSurfaceChrome + width: rollOutAdjuster.baseWidth + height: rollOutAdjuster.baseHeight opacity: contentWrapper.publishedOpacity scale: contentWrapper.scale - x: contentWrapper.x - extraLeft - y: contentWrapper.y - extraTop + x: contentWrapper.x + y: contentWrapper.y level: root.shadowLevel direction: root.effectiveShadowDirection fallbackOffset: root.shadowFallbackOffset @@ -1051,49 +1059,53 @@ Item { targetColor: contentContainer.surfaceColor borderColor: contentContainer.surfaceBorderColor borderWidth: contentContainer.surfaceBorderWidth - useCustomSource: root.usesConnectedSurfaceChrome shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome + } - Item { + // Local connected chrome (body + connector fillets joined to + // the bar edge) and its shadow as one SDF quad — no FBO. + Item { + id: localChrome + visible: root.usesLocalConnectedSurfaceChrome + + readonly property real extraLeft: (contentContainer.barTop || contentContainer.barBottom) ? Theme.connectedCornerRadius : 0 + readonly property real extraTop: (contentContainer.barLeft || contentContainer.barRight) ? Theme.connectedCornerRadius : 0 + + readonly property bool shadowsOn: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) + readonly property real shadowBlurPx: root.shadowLevel && root.shadowLevel.blurPx !== undefined ? root.shadowLevel.blurPx : 0 + readonly property real shadowSpreadPx: root.shadowLevel && root.shadowLevel.spreadPx !== undefined ? root.shadowLevel.spreadPx : 0 + readonly property real shadowOffsetX: Theme.elevationOffsetXFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset) + readonly property real shadowOffsetY: Theme.elevationOffsetYFor(root.shadowLevel, root.effectiveShadowDirection, root.shadowFallbackOffset) + readonly property color shadowTint: Theme.elevationShadowColor(root.shadowLevel) + readonly property var ambient: Theme.elevationAmbient(root.shadowLevel) + readonly property real pad: shadowsOn ? Math.ceil(Math.max(shadowBlurPx + shadowSpreadPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)), ambient.blurPx + ambient.spreadPx) + 2) : 0 + + width: rollOutAdjuster.baseWidth + extraLeft * 2 + height: rollOutAdjuster.baseHeight + extraTop * 2 + opacity: contentWrapper.publishedOpacity + scale: contentWrapper.scale + x: contentWrapper.x - extraLeft + y: contentWrapper.y - extraTop + + ShaderEffect { anchors.fill: parent - visible: root.usesLocalConnectedSurfaceChrome - clip: false + // Shadow overflow pads every side except the bar + // edge, where the silhouette must end flush. + anchors.topMargin: contentContainer.barTop ? 0 : -localChrome.pad + anchors.bottomMargin: contentContainer.barBottom ? 0 : -localChrome.pad + anchors.leftMargin: contentContainer.barLeft ? 0 : -localChrome.pad + anchors.rightMargin: contentContainer.barRight ? 0 : -localChrome.pad + fragmentShader: Qt.resolvedUrl("../Shaders/qsb/connected_chrome.frag.qsb") - Rectangle { - x: shadowSource.bodyX - y: shadowSource.bodyY - width: shadowSource.bodyWidth - height: shadowSource.bodyHeight - topLeftRadius: contentContainer.surfaceTopLeftRadius - topRightRadius: contentContainer.surfaceTopRightRadius - bottomLeftRadius: contentContainer.surfaceBottomLeftRadius - bottomRightRadius: contentContainer.surfaceBottomRightRadius - color: contentContainer.surfaceColor - } - - ConnectedCorner { - visible: root.usesConnectedSurfaceChrome - barSide: contentContainer.connectedBarSide - placement: "left" - spacing: 0 - connectorRadius: Theme.connectedCornerRadius - color: contentContainer.surfaceColor - dpr: root.dpr - x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr) - y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr) - } - - ConnectedCorner { - visible: root.usesConnectedSurfaceChrome - barSide: contentContainer.connectedBarSide - placement: "right" - spacing: 0 - connectorRadius: Theme.connectedCornerRadius - color: contentContainer.surfaceColor - dpr: root.dpr - x: Theme.snap(ConnectorGeometry.connectorX(contentContainer.connectedBarSide, shadowSource.bodyX, shadowSource.bodyWidth, placement, spacing, Theme.connectedCornerRadius), root.dpr) - y: Theme.snap(ConnectorGeometry.connectorY(contentContainer.connectedBarSide, shadowSource.bodyY, shadowSource.bodyHeight, placement, spacing, Theme.connectedCornerRadius), root.dpr) - } + property real widthPx: width + property real heightPx: height + property vector4d surfaceColor: Qt.vector4d(contentContainer.surfaceColor.r, contentContainer.surfaceColor.g, contentContainer.surfaceColor.b, contentContainer.surfaceColor.a) + property vector4d shadowColor: Qt.vector4d(localChrome.shadowTint.r, localChrome.shadowTint.g, localChrome.shadowTint.b, localChrome.shadowsOn ? localChrome.shadowTint.a : 0) + property vector4d shadowParam: Qt.vector4d(Math.max(0, localChrome.shadowBlurPx), localChrome.shadowSpreadPx, localChrome.shadowOffsetX, localChrome.shadowOffsetY) + property vector4d ambientParam: Qt.vector4d(localChrome.ambient.blurPx, localChrome.ambient.spreadPx, localChrome.shadowsOn ? localChrome.ambient.alpha : 0, 0) + property vector4d bodyRect: Qt.vector4d((contentContainer.barLeft ? 0 : localChrome.pad) + localChrome.extraLeft, (contentContainer.barTop ? 0 : localChrome.pad) + localChrome.extraTop, rollOutAdjuster.baseWidth, rollOutAdjuster.baseHeight) + property vector4d cornerRadius: Qt.vector4d(contentContainer.surfaceTopLeftRadius, contentContainer.surfaceTopRightRadius, contentContainer.surfaceBottomRightRadius, contentContainer.surfaceBottomLeftRadius) + property vector4d edgeParam: Qt.vector4d(contentContainer.barTop ? 0 : (contentContainer.barBottom ? 1 : (contentContainer.barLeft ? 2 : 3)), Theme.connectedCornerRadius, 0, 0) } }