From f545337295c156e31b83096a81fe13688993b4aa Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 13 Apr 2026 14:58:05 -0400 Subject: [PATCH] (frame): Update Connected blur Arcs & Enable shadow modes --- quickshell/Modules/Dock/Dock.qml | 30 +- quickshell/Modules/Frame/FrameWindow.qml | 261 +++++++++++++++++- .../Notifications/Popup/NotificationPopup.qml | 49 +++- .../Popup/NotificationPopupManager.qml | 79 +++++- quickshell/Widgets/ConnectedShape.qml | 1 + quickshell/Widgets/DankPopout.qml | 11 +- 6 files changed, 380 insertions(+), 51 deletions(-) diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index 3bac8f6f..875bf9f3 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -712,7 +712,7 @@ Variants { Rectangle { anchors.fill: parent - visible: !SettingsData.connectedFrameModeActive + visible: !SettingsData.connectedFrameModeActive && !(Theme.isConnectedEffect && dock.reveal) color: dock.surfaceColor topLeftRadius: dock.surfaceTopLeftRadius topRightRadius: dock.surfaceTopRightRadius @@ -722,7 +722,7 @@ Variants { Rectangle { anchors.fill: parent - visible: !SettingsData.connectedFrameModeActive + visible: !SettingsData.connectedFrameModeActive && !(Theme.isConnectedEffect && dock.reveal) color: "transparent" topLeftRadius: dock.surfaceTopLeftRadius topRightRadius: dock.surfaceTopRightRadius @@ -740,28 +740,16 @@ Variants { onHeightChanged: dock._syncDockChromeState() } - ConnectedCorner { + ConnectedShape { visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive barSide: dock.connectedBarSide - placement: "left" - spacing: 0 + bodyWidth: dockBackground.width + bodyHeight: dockBackground.height connectorRadius: Theme.connectedCornerRadius - color: dock.surfaceColor - dpr: dock._dpr - x: Theme.snap(dock.connectorX(dockBackground.x, dockBackground.width, placement, spacing), dock._dpr) - y: Theme.snap(dock.connectorY(dockBackground.y, dockBackground.height, placement, spacing), dock._dpr) - } - - ConnectedCorner { - visible: Theme.isConnectedEffect && dock.reveal && !SettingsData.connectedFrameModeActive - barSide: dock.connectedBarSide - placement: "right" - spacing: 0 - connectorRadius: Theme.connectedCornerRadius - color: dock.surfaceColor - dpr: dock._dpr - x: Theme.snap(dock.connectorX(dockBackground.x, dockBackground.width, placement, spacing), dock._dpr) - y: Theme.snap(dock.connectorY(dockBackground.y, dockBackground.height, placement, spacing), dock._dpr) + surfaceRadius: dock.surfaceRadius + fillColor: dock.surfaceColor + x: dockBackground.x - bodyX + y: dockBackground.y - bodyY } Shape { diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index b21952f9..e5c8ed85 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -1,6 +1,7 @@ pragma ComponentBehavior: Bound import QtQuick +import QtQuick.Effects import Quickshell import Quickshell.Wayland import qs.Common @@ -65,6 +66,12 @@ PanelWindow { const crossSize = isHoriz ? _popoutBodyBlurAnchor.width : _popoutBodyBlurAnchor.height; return Math.max(0, Math.min(win._ccr, extent, crossSize / 2)); } + readonly property real _effectiveNotifCcr: { + const isHoriz = win._notifState.barSide === "top" || win._notifState.barSide === "bottom"; + const crossSize = isHoriz ? _notifBodyBlurAnchor.width : _notifBodyBlurAnchor.height; + const extent = isHoriz ? _notifBodyBlurAnchor.height : _notifBodyBlurAnchor.width; + return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, extent, crossSize / 2)), 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) @@ -165,6 +172,60 @@ PanelWindow { height: _active ? _capHeight : 0 } + Item { + id: _popoutLeftConnectorBlurAnchor + opacity: 0 + + readonly property bool _active: _popoutBodyBlurAnchor._active && win._effectivePopoutCcr > 0 + readonly property real _w: win._popoutConnectorWidth(0) + readonly property real _h: win._popoutConnectorHeight(0) + + x: _active ? Theme.snap(win._popoutConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, "left", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._popoutConnectorY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, "left", 0), win._dpr) : 0 + width: _active ? _w : 0 + height: _active ? _h : 0 + } + + Item { + id: _popoutRightConnectorBlurAnchor + opacity: 0 + + readonly property bool _active: _popoutBodyBlurAnchor._active && win._effectivePopoutCcr > 0 + readonly property real _w: win._popoutConnectorWidth(0) + readonly property real _h: win._popoutConnectorHeight(0) + + x: _active ? Theme.snap(win._popoutConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, "right", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._popoutConnectorY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, "right", 0), win._dpr) : 0 + width: _active ? _w : 0 + height: _active ? _h : 0 + } + + Item { + id: _popoutLeftConnectorCutout + opacity: 0 + + readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: win._connectorArcCorner(ConnectedModeState.popoutBarSide, "left") + + x: _active ? win._connectorCutoutX(_popoutLeftConnectorBlurAnchor.x, _popoutLeftConnectorBlurAnchor.width, _arcCorner, win._effectivePopoutCcr) : 0 + y: _active ? win._connectorCutoutY(_popoutLeftConnectorBlurAnchor.y, _popoutLeftConnectorBlurAnchor.height, _arcCorner, win._effectivePopoutCcr) : 0 + width: _active ? win._effectivePopoutCcr * 2 : 0 + height: _active ? win._effectivePopoutCcr * 2 : 0 + } + + Item { + id: _popoutRightConnectorCutout + opacity: 0 + + readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: win._connectorArcCorner(ConnectedModeState.popoutBarSide, "right") + + x: _active ? win._connectorCutoutX(_popoutRightConnectorBlurAnchor.x, _popoutRightConnectorBlurAnchor.width, _arcCorner, win._effectivePopoutCcr) : 0 + y: _active ? win._connectorCutoutY(_popoutRightConnectorBlurAnchor.y, _popoutRightConnectorBlurAnchor.height, _arcCorner, win._effectivePopoutCcr) : 0 + width: _active ? win._effectivePopoutCcr * 2 : 0 + height: _active ? win._effectivePopoutCcr * 2 : 0 + } + Item { id: _dockLeftConnectorBlurAnchor opacity: 0 @@ -231,6 +292,75 @@ PanelWindow { height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0 } + Item { + id: _notifBodyBlurCap + opacity: 0 + + readonly property string _side: win._notifState.barSide + readonly property bool _active: _notifBodyBlurAnchor._active && _notifBodyBlurAnchor.width > 0 && _notifBodyBlurAnchor.height > 0 && win._notifConnectorRadius() > 0 + readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(win._notifConnectorRadius(), _notifBodyBlurAnchor.width) : _notifBodyBlurAnchor.width + readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(win._notifConnectorRadius(), _notifBodyBlurAnchor.height) : _notifBodyBlurAnchor.height + + x: !_active ? 0 : (_side === "right" ? _notifBodyBlurAnchor.x + _notifBodyBlurAnchor.width - _capWidth : _notifBodyBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _notifBodyBlurAnchor.y + _notifBodyBlurAnchor.height - _capHeight : _notifBodyBlurAnchor.y) + width: _active ? _capWidth : 0 + height: _active ? _capHeight : 0 + } + + Item { + id: _notifLeftConnectorBlurAnchor + opacity: 0 + + readonly property bool _active: _notifBodyBlurAnchor._active && win._notifConnectorRadius() > 0 + readonly property real _w: win._notifConnectorWidth(0) + readonly property real _h: win._notifConnectorHeight(0) + + x: _active ? Theme.snap(win._notifConnectorX(_notifBodyBlurAnchor.x, _notifBodyBlurAnchor.width, "left", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._notifConnectorY(_notifBodyBlurAnchor.y, _notifBodyBlurAnchor.height, "left", 0), win._dpr) : 0 + width: _active ? _w : 0 + height: _active ? _h : 0 + } + + Item { + id: _notifRightConnectorBlurAnchor + opacity: 0 + + readonly property bool _active: _notifBodyBlurAnchor._active && win._notifConnectorRadius() > 0 + readonly property real _w: win._notifConnectorWidth(0) + readonly property real _h: win._notifConnectorHeight(0) + + x: _active ? Theme.snap(win._notifConnectorX(_notifBodyBlurAnchor.x, _notifBodyBlurAnchor.width, "right", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._notifConnectorY(_notifBodyBlurAnchor.y, _notifBodyBlurAnchor.height, "right", 0), win._dpr) : 0 + width: _active ? _w : 0 + height: _active ? _h : 0 + } + + Item { + id: _notifLeftConnectorCutout + opacity: 0 + + readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: win._connectorArcCorner(win._notifState.barSide, "left") + + x: _active ? win._connectorCutoutX(_notifLeftConnectorBlurAnchor.x, _notifLeftConnectorBlurAnchor.width, _arcCorner, win._notifConnectorRadius()) : 0 + y: _active ? win._connectorCutoutY(_notifLeftConnectorBlurAnchor.y, _notifLeftConnectorBlurAnchor.height, _arcCorner, win._notifConnectorRadius()) : 0 + width: _active ? win._notifConnectorRadius() * 2 : 0 + height: _active ? win._notifConnectorRadius() * 2 : 0 + } + + Item { + id: _notifRightConnectorCutout + opacity: 0 + + readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0 + readonly property string _arcCorner: win._connectorArcCorner(win._notifState.barSide, "right") + + x: _active ? win._connectorCutoutX(_notifRightConnectorBlurAnchor.x, _notifRightConnectorBlurAnchor.width, _arcCorner, win._notifConnectorRadius()) : 0 + y: _active ? win._connectorCutoutY(_notifRightConnectorBlurAnchor.y, _notifRightConnectorBlurAnchor.height, _arcCorner, win._notifConnectorRadius()) : 0 + width: _active ? win._notifConnectorRadius() * 2 : 0 + height: _active ? win._notifConnectorRadius() * 2 : 0 + } + Region { id: _staticBlurRegion x: 0 @@ -253,6 +383,22 @@ PanelWindow { Region { item: _popoutBodyBlurCap } + Region { + item: _popoutLeftConnectorBlurAnchor + Region { + item: _popoutLeftConnectorCutout + intersection: Intersection.Subtract + radius: win._effectivePopoutCcr + } + } + Region { + item: _popoutRightConnectorBlurAnchor + Region { + item: _popoutRightConnectorCutout + intersection: Intersection.Subtract + radius: win._effectivePopoutCcr + } + } // ── Connected dock blur regions ── Region { @@ -264,7 +410,6 @@ PanelWindow { } Region { item: _dockLeftConnectorBlurAnchor - radius: win._dockConnectorRadius() Region { item: _dockLeftConnectorCutout intersection: Intersection.Subtract @@ -273,7 +418,6 @@ PanelWindow { } Region { item: _dockRightConnectorBlurAnchor - radius: win._dockConnectorRadius() Region { item: _dockRightConnectorCutout intersection: Intersection.Subtract @@ -285,9 +429,28 @@ PanelWindow { item: _notifBodyBlurAnchor radius: win._surfaceRadius } + Region { + item: _notifBodyBlurCap + } + Region { + item: _notifLeftConnectorBlurAnchor + Region { + item: _notifLeftConnectorCutout + intersection: Intersection.Subtract + radius: win._notifConnectorRadius() + } + } + Region { + item: _notifRightConnectorBlurAnchor + Region { + item: _notifRightConnectorCutout + intersection: Intersection.Subtract + radius: win._notifConnectorRadius() + } + } } - // ─── Connector position helpers (dock) ───────────────────────────────── + // ─── Connector position helpers ──────────────────────────────────────── function _dockBodyBlurRadius() { return _dockBodyBlurAnchor._active ? Math.max(0, Math.min(win._surfaceRadius, _dockBodyBlurAnchor.width / 2, _dockBodyBlurAnchor.height / 2)) : win._surfaceRadius; @@ -336,6 +499,76 @@ PanelWindow { return placement === "left" ? seamY - h : seamY; } + function _notifConnectorRadius() { + return win._effectiveNotifCcr; + } + + function _notifConnectorWidth(spacing) { + const isVert = win._notifState.barSide === "left" || win._notifState.barSide === "right"; + const radius = win._notifConnectorRadius(); + return isVert ? (spacing + radius) : radius; + } + + function _notifConnectorHeight(spacing) { + const isVert = win._notifState.barSide === "left" || win._notifState.barSide === "right"; + const radius = win._notifConnectorRadius(); + return isVert ? radius : (spacing + radius); + } + + function _notifConnectorX(baseX, bodyWidth, placement, spacing) { + const notifSide = win._notifState.barSide; + const isVert = notifSide === "left" || notifSide === "right"; + const seamX = !isVert ? (placement === "left" ? baseX : baseX + bodyWidth) : (notifSide === "left" ? baseX : baseX + bodyWidth); + const w = _notifConnectorWidth(spacing); + if (!isVert) + return placement === "left" ? seamX - w : seamX; + return notifSide === "left" ? seamX : seamX - w; + } + + function _notifConnectorY(baseY, bodyHeight, placement, spacing) { + const notifSide = win._notifState.barSide; + const seamY = notifSide === "top" ? baseY : notifSide === "bottom" ? baseY + bodyHeight : (placement === "left" ? baseY : baseY + bodyHeight); + const h = _notifConnectorHeight(spacing); + if (notifSide === "top") + return seamY; + if (notifSide === "bottom") + return seamY - h; + return placement === "left" ? seamY - h : seamY; + } + + function _popoutConnectorWidth(spacing) { + const isVert = ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right"; + const radius = win._effectivePopoutCcr; + return isVert ? (spacing + radius) : radius; + } + + function _popoutConnectorHeight(spacing) { + const isVert = ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right"; + const radius = win._effectivePopoutCcr; + return isVert ? radius : (spacing + radius); + } + + function _popoutConnectorX(baseX, bodyWidth, placement, spacing) { + const popoutSide = ConnectedModeState.popoutBarSide; + const isVert = popoutSide === "left" || popoutSide === "right"; + const seamX = !isVert ? (placement === "left" ? baseX : baseX + bodyWidth) : (popoutSide === "left" ? baseX : baseX + bodyWidth); + const w = _popoutConnectorWidth(spacing); + if (!isVert) + return placement === "left" ? seamX - w : seamX; + return popoutSide === "left" ? seamX : seamX - w; + } + + function _popoutConnectorY(baseY, bodyHeight, placement, spacing) { + const popoutSide = ConnectedModeState.popoutBarSide; + const seamY = popoutSide === "top" ? baseY : popoutSide === "bottom" ? baseY + bodyHeight : (placement === "left" ? baseY : baseY + bodyHeight); + const h = _popoutConnectorHeight(spacing); + if (popoutSide === "top") + return seamY; + if (popoutSide === "bottom") + return seamY - h; + return placement === "left" ? seamY - h : seamY; + } + function _popoutFillOverlapX() { return (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? win._seamOverlap : 0; } @@ -561,9 +794,27 @@ PanelWindow { anchors.fill: parent visible: win._connectedActive opacity: win._surfaceOpacity - layer.enabled: opacity < 1 + layer.enabled: true // Always need a layer to apply Shadow or Opacity in MultiEffect, or at least if elevationEnabled/opacity < 1 layer.smooth: false + 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: Theme.elevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" + 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 + } + FrameBorder { anchors.fill: parent borderColor: win._opaqueSurfaceColor @@ -671,7 +922,7 @@ PanelWindow { readonly property string _notifSide: win._notifState.barSide readonly property bool _isHoriz: _notifSide === "top" || _notifSide === "bottom" - readonly property real _notifCcr: Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, (_isHoriz ? _notifBodyBlurAnchor.width : _notifBodyBlurAnchor.height) / 2)), win._dpr) + readonly property real _notifCcr: win._effectiveNotifCcr readonly property real _sideUnderlap: _isHoriz ? 0 : win._seamOverlap readonly property real _bodyW: Theme.snap(_notifBodyBlurAnchor.width + _sideUnderlap, win._dpr) readonly property real _bodyH: Theme.snap(_notifBodyBlurAnchor.height, win._dpr) diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index 979a5b50..30ac5b4e 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -41,6 +41,7 @@ PanelWindow { required property string notificationId readonly property bool hasValidData: notificationData && notificationData.notification readonly property alias hovered: cardHoverHandler.hovered + readonly property alias swipeActive: content.swipeActive property int screenY: 0 property bool exiting: false property bool _isDestroying: false @@ -64,8 +65,8 @@ PanelWindow { readonly property real exitTravel: { if (directionalEffect) { if (isCenterPosition) - return content.height + entryTravel; - return content.width + entryTravel; + return Math.max(1, content.height); + return Math.max(1, content.width); } if (depthEffect) return Math.round(entryTravel * 1.35); @@ -186,7 +187,7 @@ PanelWindow { enabled: !exiting && !_isDestroying NumberAnimation { id: implicitHeightAnim - duration: Theme.variantDuration(descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration, descriptionExpanded) + duration: descriptionExpanded ? Theme.notificationExpandDuration : Theme.notificationCollapseDuration easing.type: Easing.BezierSpline easing.bezierCurve: descriptionExpanded ? Theme.variantPopoutEnterCurve : Theme.variantPopoutExitCurve onFinished: win.popupHeightChanged() @@ -237,7 +238,8 @@ PanelWindow { readonly property real maxPopupShadowBlurPx: Math.max((Theme.elevationLevel3 && Theme.elevationLevel3.blurPx !== undefined) ? Theme.elevationLevel3.blurPx : 12, (Theme.elevationLevel4 && Theme.elevationLevel4.blurPx !== undefined) ? Theme.elevationLevel4.blurPx : 16) readonly property real maxPopupShadowOffsetXPx: Math.max(Math.abs(Theme.elevationOffsetX(Theme.elevationLevel3)), Math.abs(Theme.elevationOffsetX(Theme.elevationLevel4))) readonly property real maxPopupShadowOffsetYPx: Math.max(Math.abs(Theme.elevationOffsetY(Theme.elevationLevel3, 6)), Math.abs(Theme.elevationOffsetY(Theme.elevationLevel4, 8))) - readonly property real windowShadowPad: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled ? Theme.snap(Math.max(16, maxPopupShadowBlurPx + Math.max(maxPopupShadowOffsetXPx, maxPopupShadowOffsetYPx) + 8), dpr) : 0 + readonly property bool popupWindowShadowActive: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled && !connectedFrameMode + readonly property real windowShadowPad: popupWindowShadowActive ? Theme.snap(Math.max(16, maxPopupShadowBlurPx + Math.max(maxPopupShadowOffsetXPx, maxPopupShadowOffsetYPx) + 8), dpr) : 0 anchors.top: true anchors.left: true @@ -404,7 +406,7 @@ PanelWindow { } function popupChromeMotionActive() { - return exiting || content.swipeActive || content.swipeDismissing || Math.abs(content.swipeOffset) > 0.5; + return popupChromeOpenProgress() < 1 || exiting || content.swipeActive || content.swipeDismissing || Math.abs(content.swipeOffset) > 0.5; } function popupLayoutReservesSlot() { @@ -415,13 +417,30 @@ PanelWindow { return !content.swipeDismissing; } + function _chromeMotionOffset() { + return isCenterPosition ? tx.y : tx.x; + } + + function _chromeCardTravel() { + return Math.max(1, isCenterPosition ? alignedHeight : alignedWidth); + } + + function popupChromeOpenProgress() { + if (exiting || content.swipeDismissing) + return 1; + return Math.max(0, Math.min(1, 1 - Math.abs(_chromeMotionOffset()) / _chromeCardTravel())); + } + function popupChromeReleaseProgress() { + if (exiting) + return Math.max(0, Math.min(1, Math.abs(_chromeMotionOffset()) / _chromeCardTravel())); if (content.swipeDismissing) return Math.max(0, Math.min(1, Math.abs(content.swipeOffset) / Math.max(1, content.swipeTravelDistance))); - if (!exiting) - return 0; - const exitOffset = isCenterPosition ? tx.y : tx.x; - return Math.max(0, Math.min(1, Math.abs(exitOffset) / Math.max(1, exitTravel))); + return 0; + } + + function popupChromeFollowsCardMotion() { + return content.swipeActive || (content.swipeDismissing && !exiting); } function popupChromeMotionX() { @@ -488,7 +507,7 @@ PanelWindow { win.popupChromeGeometryChanged(); } - readonly property bool shadowsAllowed: Theme.elevationEnabled && SettingsData.notificationPopupShadowEnabled + readonly property bool shadowsAllowed: win.popupWindowShadowActive readonly property var elevLevel: cardHoverHandler.hovered ? Theme.elevationLevel4 : Theme.elevationLevel3 readonly property real cardInset: Theme.snap(4, win.dpr) readonly property real shadowRenderPadding: shadowsAllowed ? Theme.snap(Math.max(16, shadowBlurPx + Math.max(Math.abs(shadowOffsetX), Math.abs(shadowOffsetY)) + 8), win.dpr) : 0 @@ -527,7 +546,7 @@ PanelWindow { shadowOffsetX: content.shadowOffsetX shadowOffsetY: content.shadowOffsetY shadowColor: content.shadowsAllowed && content.elevLevel ? Theme.elevationShadowColor(content.elevLevel) : "transparent" - shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed && !win.connectedFrameMode + shadowEnabled: !win._isDestroying && win.screenValid && content.shadowsAllowed layer.textureSize: Qt.size(Math.round(width * win.dpr), Math.round(height * win.dpr)) layer.textureMirroring: ShaderEffectSource.MirrorVertically @@ -1091,7 +1110,7 @@ PanelWindow { return isLeft ? -entryTravel : entryTravel; } to: 0 - duration: Theme.variantDuration(Theme.notificationEnterDuration, true) + duration: Theme.notificationEnterDuration easing.type: Easing.BezierSpline easing.bezierCurve: Theme.variantPopoutEnterCurve onStopped: { @@ -1124,7 +1143,7 @@ PanelWindow { const isLeft = SettingsData.notificationPopupPosition === SettingsData.Position.Left || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom; return isLeft ? -exitTravel : exitTravel; } - duration: Theme.variantDuration(Theme.notificationExitDuration, false) + duration: Theme.notificationExitDuration easing.type: Easing.BezierSpline easing.bezierCurve: Theme.variantPopoutExitCurve } @@ -1134,7 +1153,7 @@ PanelWindow { property: "opacity" from: 1 to: Theme.isDirectionalEffect ? 1 : 0 - duration: Theme.variantDuration(Theme.notificationExitDuration, false) + duration: Theme.notificationExitDuration easing.type: Easing.BezierSpline easing.bezierCurve: Theme.variantPopoutExitCurve } @@ -1144,7 +1163,7 @@ PanelWindow { property: "scale" from: 1 to: Theme.isDirectionalEffect ? 1 : Theme.effectScaleCollapsed - duration: Theme.variantDuration(Theme.notificationExitDuration, false) + duration: Theme.notificationExitDuration easing.type: Easing.BezierSpline easing.bezierCurve: Theme.variantPopoutExitCurve } diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml index 4a401bea..691ad39c 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml @@ -35,6 +35,9 @@ QtObject { property var pendingDestroys: [] property int destroyDelayMs: 100 property bool _chromeSyncPending: false + readonly property real chromeOpenProgressThreshold: 0.10 + readonly property real chromeReleaseTailStart: 0.90 + readonly property real chromeReleaseDropProgress: 0.995 property Component popupComponent popupComponent: Component { @@ -130,7 +133,20 @@ QtObject { } function _chromeWindows() { - return popupWindows.filter(p => p && p.status !== Component.Null && p.visible && !p._finalized && p.hasValidData && (p.notificationData?.popup || p.exiting)); + return popupWindows.filter(p => { + if (!p || p.status === Component.Null || !p.visible || p._finalized || !p.hasValidData) + return false; + if (!p.notificationData?.popup && !p.exiting) + return false; + if (!p.exiting && p.popupChromeOpenProgress && p.popupChromeOpenProgress() < chromeOpenProgressThreshold) + return false; + // Keep the connected shell until the card is almost fully closed. + if (p.exiting && !p.swipeActive && p.popupChromeReleaseProgress) { + if (p.popupChromeReleaseProgress() > chromeReleaseDropProgress) + return false; + } + return true; + }); } function _isFocusedScreen() { @@ -225,23 +241,72 @@ QtObject { }); } + function _clamp01(value) { + return Math.max(0, Math.min(1, value)); + } + + function _clipRectFromBarSide(rect, visibleFraction) { + const fraction = _clamp01(visibleFraction); + const w = Math.max(0, rect.right - rect.x); + const h = Math.max(0, rect.bottom - rect.y); + + if (notifBarSide === "right") { + rect.x = rect.right - w * fraction; + } else if (notifBarSide === "left") { + rect.right = rect.x + w * fraction; + } else if (notifBarSide === "bottom") { + rect.y = rect.bottom - h * fraction; + } else { + rect.bottom = rect.y + h * fraction; + } + return rect; + } + + function _popupChromeVisibleFraction(p) { + if (p.exiting && p.popupChromeReleaseProgress) + return 1 - _chromeReleaseTailProgress(p.popupChromeReleaseProgress()); + if (!p.exiting && p.popupChromeOpenProgress) + return _clamp01(p.popupChromeOpenProgress()); + return 1; + } + function _popupChromeRect(p, useMotionOffset) { if (!p || !p.screen) return null; - const motionX = useMotionOffset && p.popupChromeMotionX ? p.popupChromeMotionX() : 0; - const motionY = useMotionOffset && p.popupChromeMotionY ? p.popupChromeMotionY() : 0; - const x = (p.getContentX ? p.getContentX() : 0) + motionX; - const y = (p.getContentY ? p.getContentY() : 0) + motionY; + const x = p.getContentX ? p.getContentX() : 0; + const y = p.getContentY ? p.getContentY() : 0; const w = p.alignedWidth || 0; const h = Math.max(p.alignedHeight || 0, baseNotificationHeight); if (w <= 0 || h <= 0) return null; - return { + const rect = { x: x, y: y, right: x + w, bottom: y + h }; + + if (!useMotionOffset) + return rect; + + if (p.popupChromeFollowsCardMotion && p.popupChromeFollowsCardMotion()) { + const motionX = p.popupChromeMotionX ? p.popupChromeMotionX() : 0; + const motionY = p.popupChromeMotionY ? p.popupChromeMotionY() : 0; + rect.x += motionX; + rect.y += motionY; + rect.right += motionX; + rect.bottom += motionY; + return rect; + } + + return _clipRectFromBarSide(rect, _popupChromeVisibleFraction(p)); + } + + function _chromeReleaseTailProgress(rawProgress) { + const progress = Math.max(0, Math.min(1, rawProgress)); + if (progress <= chromeReleaseTailStart) + return 0; + return Math.max(0, Math.min(1, (progress - chromeReleaseTailStart) / Math.max(0.001, 1 - chromeReleaseTailStart))); } function _popupChromeBoundsRect(p, trailing, useMotionOffset) { @@ -249,7 +314,7 @@ QtObject { if (!rect || p !== trailing || !p.popupChromeReleaseProgress) return rect; - const progress = Math.max(0, Math.min(1, p.popupChromeReleaseProgress())); + const progress = _chromeReleaseTailProgress(p.popupChromeReleaseProgress()); if (progress <= 0) return rect; diff --git a/quickshell/Widgets/ConnectedShape.qml b/quickshell/Widgets/ConnectedShape.qml index 6a51ae4e..568b50bb 100644 --- a/quickshell/Widgets/ConnectedShape.qml +++ b/quickshell/Widgets/ConnectedShape.qml @@ -40,6 +40,7 @@ Item { anchors.fill: parent asynchronous: false preferredRendererType: Shape.CurveRenderer + antialiasing: true ShapePath { fillColor: root.fillColor diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 0f2f5930..4f4befdd 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -904,9 +904,13 @@ Item { Item { id: directionalClipMask - readonly property bool shouldClip: Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0 + readonly property bool shouldClip: (Theme.isDirectionalEffect && typeof SettingsData !== "undefined" && SettingsData.directionalAnimationMode > 0) || Theme.isConnectedEffect readonly property real clipOversize: 1000 - readonly property real connectedClipAllowance: Theme.isConnectedEffect ? Math.ceil(root.shadowRenderPadding + BlurService.borderWidth + 2) : 0 + readonly property real connectedClipAllowance: { + if (!Theme.isConnectedEffect) return 0; + if (root.frameOwnsConnectedChrome) return 0; + return -Theme.connectedCornerRadius; + } clip: shouldClip @@ -983,7 +987,7 @@ Item { borderColor: contentContainer.surfaceBorderColor borderWidth: contentContainer.surfaceBorderWidth useCustomSource: Theme.isConnectedEffect - shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" && !(root.suspendShadowWhileResizing && root._resizeActive) && !root.frameOwnsConnectedChrome Item { anchors.fill: parent @@ -1059,6 +1063,7 @@ Item { Rectangle { anchors.fill: parent + antialiasing: true topLeftRadius: contentContainer.surfaceTopLeftRadius topRightRadius: contentContainer.surfaceTopRightRadius bottomLeftRadius: contentContainer.surfaceBottomLeftRadius