From 59160ce5ac068c65399e303866042aa80cb0d88f Mon Sep 17 00:00:00 2001 From: purian23 Date: Tue, 14 Apr 2026 23:26:58 -0400 Subject: [PATCH] feat(Frame): Close the gaps --- quickshell/Common/ConnectedModeState.qml | 16 +- quickshell/Common/SettingsData.qml | 2 + quickshell/Common/settings/SettingsSpec.js | 3 +- quickshell/Modules/Frame/FrameWindow.qml | 556 +++++++++++++++--- .../Notifications/Popup/NotificationPopup.qml | 8 +- .../Popup/NotificationPopupManager.qml | 43 +- quickshell/Modules/Settings/FrameTab.qml | 16 +- quickshell/Widgets/ConnectedShape.qml | 256 ++++++-- quickshell/Widgets/DankPopout.qml | 117 +++- 9 files changed, 846 insertions(+), 171 deletions(-) diff --git a/quickshell/Common/ConnectedModeState.qml b/quickshell/Common/ConnectedModeState.qml index 1c50c2b4..b4925eff 100644 --- a/quickshell/Common/ConnectedModeState.qml +++ b/quickshell/Common/ConnectedModeState.qml @@ -29,6 +29,8 @@ Singleton { property real popoutAnimX: 0 property real popoutAnimY: 0 property string popoutScreen: "" + property bool popoutOmitStartConnector: false + property bool popoutOmitEndConnector: false // Dock state (updated by Dock when connectedFrameModeActive), keyed by screen.name property var dockStates: ({}) @@ -70,6 +72,10 @@ Singleton { popoutAnimY = Number(state.animY); if (state.screen !== undefined) popoutScreen = state.screen || ""; + if (state.omitStartConnector !== undefined) + popoutOmitStartConnector = !!state.omitStartConnector; + if (state.omitEndConnector !== undefined) + popoutOmitEndConnector = !!state.omitEndConnector; return true; } @@ -88,6 +94,8 @@ Singleton { popoutAnimX = 0; popoutAnimY = 0; popoutScreen = ""; + popoutOmitStartConnector = false; + popoutOmitEndConnector = false; return true; } @@ -175,7 +183,9 @@ Singleton { "bodyX": 0, "bodyY": 0, "bodyW": 0, - "bodyH": 0 + "bodyH": 0, + "omitStartConnector": false, + "omitEndConnector": false }) property var notificationStates: ({}) @@ -194,7 +204,9 @@ Singleton { "bodyX": Number(state && state.bodyX !== undefined ? state.bodyX : 0), "bodyY": Number(state && state.bodyY !== undefined ? state.bodyY : 0), "bodyW": Number(state && state.bodyW !== undefined ? state.bodyW : 0), - "bodyH": Number(state && state.bodyH !== undefined ? state.bodyH : 0) + "bodyH": Number(state && state.bodyH !== undefined ? state.bodyH : 0), + "omitStartConnector": !!(state && state.omitStartConnector), + "omitEndConnector": !!(state && state.omitEndConnector) }; } diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 44445425..a4897463 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -235,6 +235,8 @@ Singleton { onFrameShowOnOverviewChanged: saveSettings() property bool frameBlurEnabled: true onFrameBlurEnabledChanged: saveSettings() + property bool frameCloseGaps: false + onFrameCloseGapsChanged: saveSettings() property int previousDirectionalMode: 1 onPreviousDirectionalModeChanged: saveSettings() property var connectedFrameBarStyleBackups: ({}) diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index b67c2a36..19815e05 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -552,7 +552,8 @@ var SPEC = { frameScreenPreferences: { def: ["all"] }, frameBarSize: { def: 40 }, frameShowOnOverview: { def: false }, - frameBlurEnabled: { def: true } + frameBlurEnabled: { def: true }, + frameCloseGaps: { def: false } }; function getValidKeys() { diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index d9aa309e..3ef6d4b9 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -66,12 +66,34 @@ PanelWindow { const crossSize = isHoriz ? _popoutBodyBlurAnchor.width : _popoutBodyBlurAnchor.height; return Math.max(0, Math.min(win._ccr, extent, crossSize / 2)); } + readonly property real _effectivePopoutFarCcr: { + const isHoriz = ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom"; + const crossSize = isHoriz ? _popoutBodyBlurAnchor.width : _popoutBodyBlurAnchor.height; + return Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)); + } + readonly property real _effectivePopoutStartCcr: ConnectedModeState.popoutOmitStartConnector ? 0 : win._effectivePopoutCcr + readonly property real _effectivePopoutEndCcr: ConnectedModeState.popoutOmitEndConnector ? 0 : win._effectivePopoutCcr + readonly property real _effectivePopoutFarStartCcr: ConnectedModeState.popoutOmitStartConnector ? win._effectivePopoutFarCcr : 0 + readonly property real _effectivePopoutFarEndCcr: ConnectedModeState.popoutOmitEndConnector ? win._effectivePopoutFarCcr : 0 + readonly property real _effectivePopoutMaxCcr: Math.max(win._effectivePopoutStartCcr, win._effectivePopoutEndCcr) + readonly property real _effectivePopoutFarExtent: Math.max(win._effectivePopoutFarStartCcr, win._effectivePopoutFarEndCcr) 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 real _effectiveNotifFarCcr: { + const isHoriz = win._notifState.barSide === "top" || win._notifState.barSide === "bottom"; + const crossSize = isHoriz ? _notifBodySceneBlurAnchor.width : _notifBodySceneBlurAnchor.height; + return Theme.snap(Math.max(0, Math.min(win._ccr, win._surfaceRadius, crossSize / 2)), win._dpr); + } + readonly property real _effectiveNotifStartCcr: win._notifState.omitStartConnector ? 0 : win._effectiveNotifCcr + readonly property real _effectiveNotifEndCcr: win._notifState.omitEndConnector ? 0 : win._effectiveNotifCcr + readonly property real _effectiveNotifFarStartCcr: win._notifState.omitStartConnector ? win._effectiveNotifFarCcr : 0 + readonly property real _effectiveNotifFarEndCcr: win._notifState.omitEndConnector ? win._effectiveNotifFarCcr : 0 + readonly property real _effectiveNotifMaxCcr: Math.max(win._effectiveNotifStartCcr, win._effectiveNotifEndCcr) + readonly property real _effectiveNotifFarExtent: Math.max(win._effectiveNotifFarStartCcr, win._effectiveNotifFarEndCcr) 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) @@ -121,8 +143,8 @@ PanelWindow { readonly property bool _active: ConnectedModeState.popoutVisible && ConnectedModeState.popoutScreen === win._screenName - readonly property real _dyClamp: (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? Math.max(-ConnectedModeState.popoutBodyH, Math.min(ConnectedModeState.popoutAnimY * 1.02, ConnectedModeState.popoutBodyH)) : 0 - readonly property real _dxClamp: (ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right") ? Math.max(-ConnectedModeState.popoutBodyW, Math.min(ConnectedModeState.popoutAnimX * 1.02, ConnectedModeState.popoutBodyW)) : 0 + readonly property real _dyClamp: (ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom") ? Math.max(-ConnectedModeState.popoutBodyH, Math.min(ConnectedModeState.popoutAnimY, ConnectedModeState.popoutBodyH)) : 0 + readonly property real _dxClamp: (ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right") ? Math.max(-ConnectedModeState.popoutBodyW, Math.min(ConnectedModeState.popoutAnimX, ConnectedModeState.popoutBodyW)) : 0 x: _active ? ConnectedModeState.popoutBodyX + (ConnectedModeState.popoutBarSide === "right" ? _dxClamp : 0) : 0 y: _active ? ConnectedModeState.popoutBodyY + (ConnectedModeState.popoutBarSide === "bottom" ? _dyClamp : 0) : 0 @@ -177,9 +199,10 @@ PanelWindow { 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) + readonly property real _radius: win._popoutConnectorRadius("left") + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property real _w: win._popoutConnectorWidth(0, "left") + readonly property real _h: win._popoutConnectorHeight(0, "left") 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 @@ -191,9 +214,10 @@ PanelWindow { 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) + readonly property real _radius: win._popoutConnectorRadius("right") + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + readonly property real _w: win._popoutConnectorWidth(0, "right") + readonly property real _h: win._popoutConnectorHeight(0, "right") 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 @@ -207,11 +231,12 @@ PanelWindow { readonly property bool _active: _popoutLeftConnectorBlurAnchor.width > 0 && _popoutLeftConnectorBlurAnchor.height > 0 readonly property string _arcCorner: win._connectorArcCorner(ConnectedModeState.popoutBarSide, "left") + readonly property real _radius: win._popoutConnectorRadius("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 + x: _active ? win._connectorCutoutX(_popoutLeftConnectorBlurAnchor.x, _popoutLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_popoutLeftConnectorBlurAnchor.y, _popoutLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 } Item { @@ -220,11 +245,96 @@ PanelWindow { readonly property bool _active: _popoutRightConnectorBlurAnchor.width > 0 && _popoutRightConnectorBlurAnchor.height > 0 readonly property string _arcCorner: win._connectorArcCorner(ConnectedModeState.popoutBarSide, "right") + readonly property real _radius: win._popoutConnectorRadius("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 + x: _active ? win._connectorCutoutX(_popoutRightConnectorBlurAnchor.x, _popoutRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_popoutRightConnectorBlurAnchor.y, _popoutRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 + } + + Item { + id: _popoutFarStartConnectorBlurAnchor + opacity: 0 + + readonly property real _radius: win._effectivePopoutFarStartCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farConnectorY(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _popoutFarStartBodyBlurCap + opacity: 0 + + readonly property real _radius: win._effectivePopoutFarStartCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farBodyCapX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farBodyCapY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "left", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _popoutFarEndBodyBlurCap + opacity: 0 + + readonly property real _radius: win._effectivePopoutFarEndCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farBodyCapX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.width, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farBodyCapY(_popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _popoutFarEndConnectorBlurAnchor + opacity: 0 + + readonly property real _radius: win._effectivePopoutFarEndCcr + readonly property bool _active: _popoutBodyBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farConnectorX(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farConnectorY(_popoutBodyBlurAnchor.x, _popoutBodyBlurAnchor.y, _popoutBodyBlurAnchor.width, _popoutBodyBlurAnchor.height, ConnectedModeState.popoutBarSide, "right", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _popoutFarStartConnectorCutout + opacity: 0 + + readonly property bool _active: _popoutFarStartConnectorBlurAnchor.width > 0 && _popoutFarStartConnectorBlurAnchor.height > 0 + readonly property string _barSide: win._farConnectorBarSide(ConnectedModeState.popoutBarSide, "left") + readonly property string _placement: win._farConnectorPlacement(ConnectedModeState.popoutBarSide, "left") + readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement) + readonly property real _radius: win._effectivePopoutFarStartCcr + + x: _active ? win._connectorCutoutX(_popoutFarStartConnectorBlurAnchor.x, _popoutFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_popoutFarStartConnectorBlurAnchor.y, _popoutFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 + } + + Item { + id: _popoutFarEndConnectorCutout + opacity: 0 + + readonly property bool _active: _popoutFarEndConnectorBlurAnchor.width > 0 && _popoutFarEndConnectorBlurAnchor.height > 0 + readonly property string _barSide: win._farConnectorBarSide(ConnectedModeState.popoutBarSide, "right") + readonly property string _placement: win._farConnectorPlacement(ConnectedModeState.popoutBarSide, "right") + readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement) + readonly property real _radius: win._effectivePopoutFarEndCcr + + x: _active ? win._connectorCutoutX(_popoutFarEndConnectorBlurAnchor.x, _popoutFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_popoutFarEndConnectorBlurAnchor.y, _popoutFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 } Item { @@ -293,17 +403,30 @@ PanelWindow { height: _active ? Theme.snap(win._notifState.bodyH, win._dpr) : 0 } + Item { + id: _notifBodySceneBlurAnchor + visible: false + + readonly property bool _active: _notifBodyBlurAnchor._active + + x: _active ? Theme.snap(win._notifBodySceneX(), win._dpr) : 0 + y: _active ? Theme.snap(win._notifBodySceneY(), win._dpr) : 0 + width: _active ? Theme.snap(win._notifBodySceneWidth(), win._dpr) : 0 + height: _active ? Theme.snap(win._notifBodySceneHeight(), 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 + readonly property real _capRadius: win._notifMaxConnectorRadius() + readonly property bool _active: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 && _capRadius > 0 + readonly property real _capWidth: (_side === "left" || _side === "right") ? Math.min(_capRadius, _notifBodySceneBlurAnchor.width) : _notifBodySceneBlurAnchor.width + readonly property real _capHeight: (_side === "top" || _side === "bottom") ? Math.min(_capRadius, _notifBodySceneBlurAnchor.height) : _notifBodySceneBlurAnchor.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) + x: !_active ? 0 : (_side === "right" ? _notifBodySceneBlurAnchor.x + _notifBodySceneBlurAnchor.width - _capWidth : _notifBodySceneBlurAnchor.x) + y: !_active ? 0 : (_side === "bottom" ? _notifBodySceneBlurAnchor.y + _notifBodySceneBlurAnchor.height - _capHeight : _notifBodySceneBlurAnchor.y) width: _active ? _capWidth : 0 height: _active ? _capHeight : 0 } @@ -312,12 +435,13 @@ PanelWindow { 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) + readonly property real _radius: win._notifConnectorRadius("left") + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property real _w: win._notifConnectorWidth(0, "left") + readonly property real _h: win._notifConnectorHeight(0, "left") - 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 + x: _active ? Theme.snap(win._notifConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, "left", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._notifConnectorY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, "left", 0), win._dpr) : 0 width: _active ? _w : 0 height: _active ? _h : 0 } @@ -326,12 +450,13 @@ PanelWindow { 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) + readonly property real _radius: win._notifConnectorRadius("right") + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + readonly property real _w: win._notifConnectorWidth(0, "right") + readonly property real _h: win._notifConnectorHeight(0, "right") - 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 + x: _active ? Theme.snap(win._notifConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, "right", 0), win._dpr) : 0 + y: _active ? Theme.snap(win._notifConnectorY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, "right", 0), win._dpr) : 0 width: _active ? _w : 0 height: _active ? _h : 0 } @@ -342,11 +467,12 @@ PanelWindow { readonly property bool _active: _notifLeftConnectorBlurAnchor.width > 0 && _notifLeftConnectorBlurAnchor.height > 0 readonly property string _arcCorner: win._connectorArcCorner(win._notifState.barSide, "left") + readonly property real _radius: win._notifConnectorRadius("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 + x: _active ? win._connectorCutoutX(_notifLeftConnectorBlurAnchor.x, _notifLeftConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_notifLeftConnectorBlurAnchor.y, _notifLeftConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 } Item { @@ -355,11 +481,96 @@ PanelWindow { readonly property bool _active: _notifRightConnectorBlurAnchor.width > 0 && _notifRightConnectorBlurAnchor.height > 0 readonly property string _arcCorner: win._connectorArcCorner(win._notifState.barSide, "right") + readonly property real _radius: win._notifConnectorRadius("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 + x: _active ? win._connectorCutoutX(_notifRightConnectorBlurAnchor.x, _notifRightConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_notifRightConnectorBlurAnchor.y, _notifRightConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 + } + + Item { + id: _notifFarStartConnectorBlurAnchor + opacity: 0 + + readonly property real _radius: win._effectiveNotifFarStartCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farConnectorY(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _notifFarStartBodyBlurCap + opacity: 0 + + readonly property real _radius: win._effectiveNotifFarStartCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farBodyCapX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, win._notifState.barSide, "left", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farBodyCapY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "left", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _notifFarEndBodyBlurCap + opacity: 0 + + readonly property real _radius: win._effectiveNotifFarEndCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farBodyCapX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.width, win._notifState.barSide, "right", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farBodyCapY(_notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _notifFarEndConnectorBlurAnchor + opacity: 0 + + readonly property real _radius: win._effectiveNotifFarEndCcr + readonly property bool _active: _notifBodySceneBlurAnchor._active && _radius > 0 + + x: _active ? Theme.snap(win._farConnectorX(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 + y: _active ? Theme.snap(win._farConnectorY(_notifBodySceneBlurAnchor.x, _notifBodySceneBlurAnchor.y, _notifBodySceneBlurAnchor.width, _notifBodySceneBlurAnchor.height, win._notifState.barSide, "right", _radius), win._dpr) : 0 + width: _active ? _radius : 0 + height: _active ? _radius : 0 + } + + Item { + id: _notifFarStartConnectorCutout + opacity: 0 + + readonly property bool _active: _notifFarStartConnectorBlurAnchor.width > 0 && _notifFarStartConnectorBlurAnchor.height > 0 + readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "left") + readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "left") + readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement) + readonly property real _radius: win._effectiveNotifFarStartCcr + + x: _active ? win._connectorCutoutX(_notifFarStartConnectorBlurAnchor.x, _notifFarStartConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_notifFarStartConnectorBlurAnchor.y, _notifFarStartConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 + } + + Item { + id: _notifFarEndConnectorCutout + opacity: 0 + + readonly property bool _active: _notifFarEndConnectorBlurAnchor.width > 0 && _notifFarEndConnectorBlurAnchor.height > 0 + readonly property string _barSide: win._farConnectorBarSide(win._notifState.barSide, "right") + readonly property string _placement: win._farConnectorPlacement(win._notifState.barSide, "right") + readonly property string _arcCorner: win._connectorArcCorner(_barSide, _placement) + readonly property real _radius: win._effectiveNotifFarEndCcr + + x: _active ? win._connectorCutoutX(_notifFarEndConnectorBlurAnchor.x, _notifFarEndConnectorBlurAnchor.width, _arcCorner, _radius) : 0 + y: _active ? win._connectorCutoutY(_notifFarEndConnectorBlurAnchor.y, _notifFarEndConnectorBlurAnchor.height, _arcCorner, _radius) : 0 + width: _active ? _radius * 2 : 0 + height: _active ? _radius * 2 : 0 } Region { @@ -389,7 +600,7 @@ PanelWindow { Region { item: _popoutLeftConnectorCutout intersection: Intersection.Subtract - radius: win._effectivePopoutCcr + radius: win._popoutConnectorRadius("left") } } Region { @@ -397,7 +608,29 @@ PanelWindow { Region { item: _popoutRightConnectorCutout intersection: Intersection.Subtract - radius: win._effectivePopoutCcr + radius: win._popoutConnectorRadius("right") + } + } + Region { + item: _popoutFarStartBodyBlurCap + } + Region { + item: _popoutFarEndBodyBlurCap + } + Region { + item: _popoutFarStartConnectorBlurAnchor + Region { + item: _popoutFarStartConnectorCutout + intersection: Intersection.Subtract + radius: win._effectivePopoutFarStartCcr + } + } + Region { + item: _popoutFarEndConnectorBlurAnchor + Region { + item: _popoutFarEndConnectorCutout + intersection: Intersection.Subtract + radius: win._effectivePopoutFarEndCcr } } @@ -427,7 +660,7 @@ PanelWindow { } Region { - item: _notifBodyBlurAnchor + item: _notifBodySceneBlurAnchor radius: win._surfaceRadius } Region { @@ -438,7 +671,7 @@ PanelWindow { Region { item: _notifLeftConnectorCutout intersection: Intersection.Subtract - radius: win._notifConnectorRadius() + radius: win._notifConnectorRadius("left") } } Region { @@ -446,7 +679,29 @@ PanelWindow { Region { item: _notifRightConnectorCutout intersection: Intersection.Subtract - radius: win._notifConnectorRadius() + radius: win._notifConnectorRadius("right") + } + } + Region { + item: _notifFarStartBodyBlurCap + } + Region { + item: _notifFarEndBodyBlurCap + } + Region { + item: _notifFarStartConnectorBlurAnchor + Region { + item: _notifFarStartConnectorCutout + intersection: Intersection.Subtract + radius: win._effectiveNotifFarStartCcr + } + } + Region { + item: _notifFarEndConnectorBlurAnchor + Region { + item: _notifFarEndConnectorCutout + intersection: Intersection.Subtract + radius: win._effectiveNotifFarEndCcr } } } @@ -500,19 +755,68 @@ PanelWindow { return placement === "left" ? seamY - h : seamY; } - function _notifConnectorRadius() { - return win._effectiveNotifCcr; + function _notifSideUnderlap() { + const side = win._notifState.barSide; + return (side === "left" || side === "right") ? win._seamOverlap : 0; } - function _notifConnectorWidth(spacing) { + function _notifStartUnderlap() { + return win._notifState.omitStartConnector ? win._seamOverlap : 0; + } + + function _notifEndUnderlap() { + return win._notifState.omitEndConnector ? win._seamOverlap : 0; + } + + function _notifBodySceneX() { + const side = win._notifState.barSide; + const isHoriz = side === "top" || side === "bottom"; + if (isHoriz) + return _notifBodyBlurAnchor.x - win._notifStartUnderlap(); + return _notifBodyBlurAnchor.x - (side === "left" ? win._notifSideUnderlap() : 0); + } + + function _notifBodySceneY() { + const side = win._notifState.barSide; + const isHoriz = side === "top" || side === "bottom"; + if (isHoriz) + return _notifBodyBlurAnchor.y; + return _notifBodyBlurAnchor.y - win._notifStartUnderlap(); + } + + function _notifBodySceneWidth() { + const side = win._notifState.barSide; + const isHoriz = side === "top" || side === "bottom"; + if (isHoriz) + return _notifBodyBlurAnchor.width + win._notifStartUnderlap() + win._notifEndUnderlap(); + return _notifBodyBlurAnchor.width + win._notifSideUnderlap(); + } + + function _notifBodySceneHeight() { + const side = win._notifState.barSide; + const isHoriz = side === "top" || side === "bottom"; + if (isHoriz) + return _notifBodyBlurAnchor.height; + return _notifBodyBlurAnchor.height + win._notifStartUnderlap() + win._notifEndUnderlap(); + } + + function _notifConnectorRadius(placement) { + return placement === "right" ? win._effectiveNotifEndCcr : win._effectiveNotifStartCcr; + } + + function _notifMaxConnectorRadius() { + return win._effectiveNotifMaxCcr; + } + + function _notifConnectorWidth(spacing, placement) { const isVert = win._notifState.barSide === "left" || win._notifState.barSide === "right"; - const radius = win._notifConnectorRadius(); + const radius = win._notifConnectorRadius(placement); return isVert ? (spacing + radius) : radius; } - function _notifConnectorHeight(spacing) { + function _notifConnectorHeight(spacing, placement) { const isVert = win._notifState.barSide === "left" || win._notifState.barSide === "right"; - const radius = win._notifConnectorRadius(); + const radius = win._notifConnectorRadius(placement); return isVert ? radius : (spacing + radius); } @@ -520,7 +824,7 @@ PanelWindow { 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); + const w = _notifConnectorWidth(spacing, placement); if (!isVert) return placement === "left" ? seamX - w : seamX; return notifSide === "left" ? seamX : seamX - w; @@ -529,7 +833,7 @@ PanelWindow { 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); + const h = _notifConnectorHeight(spacing, placement); if (notifSide === "top") return seamY; if (notifSide === "bottom") @@ -537,15 +841,19 @@ PanelWindow { return placement === "left" ? seamY - h : seamY; } - function _popoutConnectorWidth(spacing) { + function _popoutConnectorRadius(placement) { + return placement === "right" ? win._effectivePopoutEndCcr : win._effectivePopoutStartCcr; + } + + function _popoutConnectorWidth(spacing, placement) { const isVert = ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right"; - const radius = win._effectivePopoutCcr; + const radius = win._popoutConnectorRadius(placement); return isVert ? (spacing + radius) : radius; } - function _popoutConnectorHeight(spacing) { + function _popoutConnectorHeight(spacing, placement) { const isVert = ConnectedModeState.popoutBarSide === "left" || ConnectedModeState.popoutBarSide === "right"; - const radius = win._effectivePopoutCcr; + const radius = win._popoutConnectorRadius(placement); return isVert ? radius : (spacing + radius); } @@ -553,7 +861,7 @@ PanelWindow { 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); + const w = _popoutConnectorWidth(spacing, placement); if (!isVert) return placement === "left" ? seamX - w : seamX; return popoutSide === "left" ? seamX : seamX - w; @@ -562,7 +870,7 @@ PanelWindow { 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); + const h = _popoutConnectorHeight(spacing, placement); if (popoutSide === "top") return seamY; if (popoutSide === "bottom") @@ -598,27 +906,27 @@ PanelWindow { function _popoutBlurCapThickness() { const extent = win._popoutArcExtent(); - return Math.max(0, Math.min(win._effectivePopoutCcr, extent - win._surfaceRadius)); + return Math.max(0, Math.min(win._effectivePopoutMaxCcr, extent - win._surfaceRadius)); } function _popoutChromeX() { const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyX - ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutCcr : 0); + return ConnectedModeState.popoutBodyX - ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr : 0); } function _popoutChromeY() { const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyY - ((barSide === "left" || barSide === "right") ? win._effectivePopoutCcr : 0); + return ConnectedModeState.popoutBodyY - ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr : 0); } function _popoutChromeWidth() { const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyW + ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutCcr * 2 : 0); + return ConnectedModeState.popoutBodyW + ((barSide === "top" || barSide === "bottom") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); } function _popoutChromeHeight() { const barSide = ConnectedModeState.popoutBarSide; - return ConnectedModeState.popoutBodyH + ((barSide === "left" || barSide === "right") ? win._effectivePopoutCcr * 2 : 0); + return ConnectedModeState.popoutBodyH + ((barSide === "left" || barSide === "right") ? win._effectivePopoutStartCcr + win._effectivePopoutEndCcr : 0); } function _popoutClipX() { @@ -637,6 +945,34 @@ PanelWindow { return _popoutBodyBlurAnchor.height + win._popoutFillOverlapY() * 2; } + function _popoutShapeBodyOffsetX() { + const side = ConnectedModeState.popoutBarSide; + if (side === "top" || side === "bottom") + return win._effectivePopoutStartCcr; + return side === "right" ? win._effectivePopoutFarExtent : 0; + } + + function _popoutShapeBodyOffsetY() { + const side = ConnectedModeState.popoutBarSide; + if (side === "top" || side === "bottom") + return side === "bottom" ? win._effectivePopoutFarExtent : 0; + return win._effectivePopoutStartCcr; + } + + function _popoutShapeWidth() { + const side = ConnectedModeState.popoutBarSide; + if (side === "top" || side === "bottom") + return win._popoutClipWidth() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; + return win._popoutClipWidth() + win._effectivePopoutFarExtent; + } + + function _popoutShapeHeight() { + const side = ConnectedModeState.popoutBarSide; + if (side === "top" || side === "bottom") + return win._popoutClipHeight() + win._effectivePopoutFarExtent; + return win._popoutClipHeight() + win._effectivePopoutStartCcr + win._effectivePopoutEndCcr; + } + function _popoutBodyXInClip() { return (ConnectedModeState.popoutBarSide === "left" ? _popoutBodyBlurAnchor._dxClamp : 0) - win._popoutFillOverlapX(); } @@ -681,6 +1017,54 @@ PanelWindow { return ((win._dockState.barSide === "left" || win._dockState.barSide === "right") ? win._dockConnectorRadius() : 0) - win._dockFillOverlapY(); } + function _farConnectorBarSide(sourceSide, placement) { + if (sourceSide === "top" || sourceSide === "bottom") + return placement === "left" ? "left" : "right"; + return placement === "left" ? "top" : "bottom"; + } + + function _farConnectorPlacement(sourceSide, placement) { + if (sourceSide === "top") + return "right"; + if (sourceSide === "bottom") + return "left"; + if (sourceSide === "left") + return "right"; + return "left"; + } + + function _farConnectorX(baseX, baseY, bodyWidth, bodyHeight, sourceSide, placement, radius) { + if (sourceSide === "top" || sourceSide === "bottom") + return placement === "left" ? baseX : baseX + bodyWidth - radius; + if (sourceSide === "left") + return baseX + bodyWidth; + return baseX - radius; + } + + function _farConnectorY(baseX, baseY, bodyWidth, bodyHeight, sourceSide, placement, radius) { + if (sourceSide === "top") + return baseY + bodyHeight; + if (sourceSide === "bottom") + return baseY - radius; + return placement === "left" ? baseY : baseY + bodyHeight - radius; + } + + function _farBodyCapX(baseX, bodyWidth, sourceSide, placement, radius) { + if (sourceSide === "top" || sourceSide === "bottom") + return placement === "left" ? baseX : baseX + bodyWidth - radius; + if (sourceSide === "left") + return baseX + bodyWidth - radius; + return baseX; + } + + function _farBodyCapY(baseY, bodyHeight, sourceSide, placement, radius) { + if (sourceSide === "top") + return baseY + bodyHeight - radius; + if (sourceSide === "bottom") + return baseY; + return placement === "left" ? baseY : baseY + bodyHeight - radius; + } + function _connectorArcCorner(barSide, placement) { if (barSide === "top") return placement === "left" ? "bottomLeft" : "bottomRight"; @@ -756,6 +1140,9 @@ PanelWindow { function onConnectedFrameModeActiveChanged() { _blurRebuildTimer.restart(); } + function onFrameCloseGapsChanged() { + _blurRebuildTimer.restart(); + } } Connections { @@ -842,12 +1229,10 @@ PanelWindow { Item { id: _popoutClip - readonly property bool _barHoriz: ConnectedModeState.popoutBarSide === "top" || ConnectedModeState.popoutBarSide === "bottom" - // Expand clip by ccr on bar axis to include arc columns - x: win._popoutClipX() - (_barHoriz ? win._effectivePopoutCcr : 0) - y: win._popoutClipY() - (_barHoriz ? 0 : win._effectivePopoutCcr) - width: win._popoutClipWidth() + (_barHoriz ? win._effectivePopoutCcr * 2 : 0) - height: win._popoutClipHeight() + (_barHoriz ? 0 : win._effectivePopoutCcr * 2) + x: win._popoutClipX() - win._popoutShapeBodyOffsetX() + y: win._popoutClipY() - win._popoutShapeBodyOffsetY() + width: win._popoutShapeWidth() + height: win._popoutShapeHeight() clip: true ConnectedShape { @@ -857,6 +1242,10 @@ PanelWindow { 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 @@ -920,27 +1309,34 @@ PanelWindow { Item { id: _notifChrome - visible: _notifBodyBlurAnchor._active + visible: _notifBodySceneBlurAnchor._active readonly property string _notifSide: win._notifState.barSide readonly property bool _isHoriz: _notifSide === "top" || _notifSide === "bottom" - 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) + readonly property real _startCcr: win._effectiveNotifStartCcr + readonly property real _endCcr: win._effectiveNotifEndCcr + readonly property real _farExtent: win._effectiveNotifFarExtent + readonly property real _bodyOffsetX: _isHoriz ? _startCcr : (_notifSide === "right" ? _farExtent : 0) + readonly property real _bodyOffsetY: _isHoriz ? (_notifSide === "bottom" ? _farExtent : 0) : _startCcr + readonly property real _bodyW: Theme.snap(_notifBodySceneBlurAnchor.width, win._dpr) + readonly property real _bodyH: Theme.snap(_notifBodySceneBlurAnchor.height, win._dpr) z: _isHoriz ? 0 : -1 - x: Theme.snap(_notifBodyBlurAnchor.x - (_isHoriz ? _notifCcr : (_notifSide === "left" ? _sideUnderlap : 0)), win._dpr) - y: Theme.snap(_notifBodyBlurAnchor.y - (_isHoriz ? 0 : _notifCcr), win._dpr) - width: _isHoriz ? Theme.snap(_bodyW + _notifCcr * 2, win._dpr) : _bodyW - height: Theme.snap(_bodyH + (_isHoriz ? 0 : _notifCcr * 2), win._dpr) + x: Theme.snap(_notifBodySceneBlurAnchor.x - _bodyOffsetX, win._dpr) + y: Theme.snap(_notifBodySceneBlurAnchor.y - _bodyOffsetY, win._dpr) + width: _isHoriz ? Theme.snap(_bodyW + _startCcr + _endCcr, win._dpr) : Theme.snap(_bodyW + _farExtent, win._dpr) + height: _isHoriz ? Theme.snap(_bodyH + _farExtent, win._dpr) : Theme.snap(_bodyH + _startCcr + _endCcr, win._dpr) ConnectedShape { - visible: _notifBodyBlurAnchor._active && _notifBodyBlurAnchor.width > 0 && _notifBodyBlurAnchor.height > 0 + visible: _notifBodySceneBlurAnchor._active && _notifBodySceneBlurAnchor.width > 0 && _notifBodySceneBlurAnchor.height > 0 barSide: _notifChrome._notifSide bodyWidth: _notifChrome._bodyW bodyHeight: _notifChrome._bodyH - connectorRadius: _notifChrome._notifCcr + connectorRadius: win._effectiveNotifCcr + startConnectorRadius: _notifChrome._startCcr + endConnectorRadius: _notifChrome._endCcr + farStartConnectorRadius: win._effectiveNotifFarStartCcr + farEndConnectorRadius: win._effectiveNotifFarEndCcr surfaceRadius: win._surfaceRadius fillColor: win._opaqueSurfaceColor x: 0 diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml index d9a13d4d..e8701df2 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopup.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopup.qml @@ -307,7 +307,9 @@ PanelWindow { return 0; if (connectedFrameMode) { - const cornerClear = isCenterPosition ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); + const cornerClear = (isCenterPosition || SettingsData.frameCloseGaps) + ? 0 + : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); return _frameEdgeInset("top") + cornerClear + screenY; } const barInfo = getBarInfo(); @@ -322,7 +324,9 @@ PanelWindow { return 0; if (connectedFrameMode) { - const cornerClear = isCenterPosition ? 0 : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); + const cornerClear = (isCenterPosition || SettingsData.frameCloseGaps) + ? 0 + : (Theme.px(SettingsData.frameRounding, dpr) + Theme.px(Theme.connectedCornerRadius, dpr)); return _frameEdgeInset("bottom") + cornerClear + screenY; } const barInfo = getBarInfo(); diff --git a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml index e18597be..5c9267d5 100644 --- a/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml +++ b/quickshell/Modules/Notifications/Popup/NotificationPopupManager.qml @@ -11,6 +11,7 @@ QtObject { readonly property bool notificationConnectedMode: SettingsData.frameEnabled && Theme.isConnectedEffect && SettingsData.isScreenInPreferences(manager.modelData, SettingsData.frameScreenPreferences) + readonly property bool closeGapNotifications: notificationConnectedMode && SettingsData.frameCloseGaps readonly property string notifBarSide: { const pos = SettingsData.notificationPopupPosition; if (pos === -1) return "top"; @@ -339,6 +340,23 @@ QtObject { return pos === -1 || pos === SettingsData.Position.Top || pos === SettingsData.Position.Left; } + function _frameEdgeInset(side) { + if (!manager.modelData) + return 0; + const edges = SettingsData.getActiveBarEdgesForScreen(manager.modelData); + const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness; + const dpr = CompositorService.getScreenScale(manager.modelData); + return Math.max(0, Math.round(Theme.px(raw, dpr))); + } + + function _closeGapChromeAnchorEdge(anchorsTop) { + if (!closeGapNotifications || !manager.modelData) + return null; + if (anchorsTop) + return _frameEdgeInset("top") + topMargin; + return manager.modelData.height - _frameEdgeInset("bottom") - topMargin; + } + function _trailingChromeWindow(candidates) { const anchorsTop = _stackAnchorsTop(); let trailing = null; @@ -429,6 +447,14 @@ QtObject { if (!stackEdge.anchorsTop && stackEdge.edge > maxYEnd) maxYEnd = stackEdge.edge; } + const anchorsTop = stackEdge !== null ? stackEdge.anchorsTop : _stackAnchorsTop(); + const closeGapAnchorEdge = _closeGapChromeAnchorEdge(anchorsTop); + if (closeGapAnchorEdge !== null) { + if (anchorsTop) + minY = closeGapAnchorEdge; + else + maxYEnd = closeGapAnchorEdge; + } if (minX === Infinity || minY === Infinity || maxXEnd <= minX || maxYEnd <= minY) { ConnectedModeState.clearNotificationState(screenName); return; @@ -439,10 +465,24 @@ QtObject { bodyX: minX, bodyY: minY, bodyW: maxXEnd - minX, - bodyH: maxYEnd - minY + bodyH: maxYEnd - minY, + omitStartConnector: _notificationOmitStartConnector(), + omitEndConnector: _notificationOmitEndConnector() }); } + function _notificationOmitStartConnector() { + return closeGapNotifications + && (SettingsData.notificationPopupPosition === SettingsData.Position.Top + || SettingsData.notificationPopupPosition === SettingsData.Position.Left); + } + + function _notificationOmitEndConnector() { + return closeGapNotifications + && (SettingsData.notificationPopupPosition === SettingsData.Position.Right + || SettingsData.notificationPopupPosition === SettingsData.Position.Bottom); + } + function _onPopupChromeGeometryChanged(p) { if (!p || popupWindows.indexOf(p) === -1) return; @@ -504,6 +544,7 @@ QtObject { } onNotificationConnectedModeChanged: _scheduleNotificationChromeSync() + onCloseGapNotificationsChanged: _scheduleNotificationChromeSync() onNotifBarSideChanged: _scheduleNotificationChromeSync() onModelDataChanged: _scheduleNotificationChromeSync() onTopMarginChanged: _repositionAll() diff --git a/quickshell/Modules/Settings/FrameTab.qml b/quickshell/Modules/Settings/FrameTab.qml index 6d4644e1..037b0518 100644 --- a/quickshell/Modules/Settings/FrameTab.qml +++ b/quickshell/Modules/Settings/FrameTab.qml @@ -262,7 +262,7 @@ Item { SettingsCard { width: parent.width iconName: "toolbar" - title: I18n.tr("Bar Integration") + title: I18n.tr("Integrations") settingKey: "frameBarIntegration" collapsible: true expanded: true @@ -283,7 +283,7 @@ Item { settingKey: "directionalAnimationMode" tags: ["frame", "connected", "popout", "corner", "animation"] text: I18n.tr("Connected Mode") - description: I18n.tr("Popouts emerge flush from the bar edge as one continuous piece (based on Slide)") + description: I18n.tr("Popouts emerge flush from the bar edge as one continuous piece") checked: SettingsData.connectedFrameModeActive onToggled: checked => { if (checked) { @@ -302,6 +302,18 @@ Item { function onMotionEffectChanged() {} } } + + SettingsToggleRow { + visible: SettingsData.frameEnabled + settingKey: "frameCloseGaps" + tags: ["frame", "connected", "gap", "edge", "flush", "popout", "notification"] + text: I18n.tr("Close the Gaps") + description: I18n.tr("Connected popouts and notification corners sit flush against the frame edge") + checked: SettingsData.frameCloseGaps + enabled: SettingsData.connectedFrameModeActive + opacity: enabled ? 1.0 : 0.5 + onToggled: checked => SettingsData.set("frameCloseGaps", checked) + } } // ── Display Assignment ──────────────────────────────────────────── diff --git a/quickshell/Widgets/ConnectedShape.qml b/quickshell/Widgets/ConnectedShape.qml index 568b50bb..82a822c1 100644 --- a/quickshell/Widgets/ConnectedShape.qml +++ b/quickshell/Widgets/ConnectedShape.qml @@ -2,8 +2,8 @@ import QtQuick import QtQuick.Shapes import qs.Common -// Unified connected silhouette: body + concave arcs as one ShapePath. -// PathArc pattern — 4 arcs + 4 lines, no sibling alignment. +// Unified connected silhouette: body + near/far concave arcs as one ShapePath. +// Keeping the connected chrome in one path avoids sibling alignment seams. Item { id: root @@ -14,6 +14,10 @@ Item { property real bodyHeight: 0 property real connectorRadius: 12 + property real startConnectorRadius: connectorRadius + property real endConnectorRadius: connectorRadius + property real farStartConnectorRadius: 0 + property real farEndConnectorRadius: 0 property real surfaceRadius: 12 @@ -21,20 +25,36 @@ Item { // ── Derived layout ── readonly property bool _horiz: barSide === "top" || barSide === "bottom" - readonly property real _cr: Math.max(0, connectorRadius) + readonly property real _sc: Math.max(0, startConnectorRadius) + readonly property real _ec: Math.max(0, endConnectorRadius) + readonly property real _fsc: Math.max(0, farStartConnectorRadius) + readonly property real _fec: Math.max(0, farEndConnectorRadius) + readonly property real _firstCr: barSide === "left" ? _sc : _ec + readonly property real _secondCr: barSide === "left" ? _ec : _sc + readonly property real _firstFarCr: barSide === "left" ? _fsc : _fec + readonly property real _secondFarCr: barSide === "left" ? _fec : _fsc + readonly property real _farExtent: Math.max(_fsc, _fec) readonly property real _sr: Math.max(0, Math.min(surfaceRadius, (_horiz ? bodyWidth : bodyHeight) / 2, (_horiz ? bodyHeight : bodyWidth) / 2)) + readonly property real _firstSr: _firstFarCr > 0 ? 0 : _sr + readonly property real _secondSr: _secondFarCr > 0 ? 0 : _sr + readonly property real _firstFarInset: _firstFarCr > 0 ? _firstFarCr : _firstSr + readonly property real _secondFarInset: _secondFarCr > 0 ? _secondFarCr : _secondSr // Root-level aliases — PathArc/PathLine elements can't use `parent`. readonly property real _bw: bodyWidth readonly property real _bh: bodyHeight - readonly property real _totalW: _horiz ? _bw + _cr * 2 : _bw - readonly property real _totalH: _horiz ? _bh : _bh + _cr * 2 + readonly property real _bodyLeft: _horiz ? _sc : (barSide === "right" ? _farExtent : 0) + readonly property real _bodyRight: _bodyLeft + _bw + readonly property real _bodyTop: _horiz ? (barSide === "bottom" ? _farExtent : 0) : _sc + readonly property real _bodyBottom: _bodyTop + _bh + readonly property real _totalW: _horiz ? _bw + _sc + _ec : _bw + _farExtent + readonly property real _totalH: _horiz ? _bh + _farExtent : _bh + _sc + _ec width: _totalW height: _totalH - readonly property real bodyX: _horiz ? _cr : 0 - readonly property real bodyY: _horiz ? 0 : _cr + readonly property real bodyX: root._bodyLeft + readonly property real bodyY: root._bodyTop Shape { anchors.fill: parent @@ -94,27 +114,27 @@ Item { relativeX: { switch (root.barSide) { case "left": - return root._cr; + return root._firstCr; case "right": - return -root._cr; + return -root._firstCr; default: - return -root._cr; + return -root._firstCr; } } relativeY: { switch (root.barSide) { case "bottom": - return -root._cr; + return -root._firstCr; case "left": - return root._cr; + return root._firstCr; case "right": - return -root._cr; + return -root._firstCr; default: - return root._cr; + return root._firstCr; } } - radiusX: root._cr - radiusY: root._cr + radiusX: root._firstCr + radiusY: root._firstCr direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise } @@ -123,23 +143,23 @@ Item { x: { switch (root.barSide) { case "left": - return root._bw - root._sr; + return root._bodyRight - root._firstSr; case "right": - return root._sr; + return root._bodyLeft + root._firstSr; default: - return root._totalW - root._cr; + return root._bodyRight; } } y: { switch (root.barSide) { case "bottom": - return root._sr; + return root._bodyTop + root._firstSr; case "left": - return root._cr; + return root._bodyTop; case "right": - return root._cr + root._bh; + return root._bodyBottom; default: - return root._totalH - root._sr; + return root._bodyBottom - root._firstSr; } } } @@ -149,52 +169,160 @@ Item { relativeX: { switch (root.barSide) { case "left": - return root._sr; + return root._firstSr; case "right": - return -root._sr; + return -root._firstSr; default: - return -root._sr; + return -root._firstSr; } } relativeY: { switch (root.barSide) { case "bottom": - return -root._sr; + return -root._firstSr; case "left": - return root._sr; + return root._firstSr; case "right": - return -root._sr; + return -root._firstSr; default: - return root._sr; + return root._firstSr; } } - radiusX: root._sr - radiusY: root._sr + radiusX: root._firstSr + radiusY: root._firstSr direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise } + // Opposite-side connector 1 + PathLine { + x: { + switch (root.barSide) { + case "left": + return root._firstFarCr > 0 ? root._bodyRight + root._firstFarCr : root._bodyRight; + case "right": + return root._firstFarCr > 0 ? root._bodyLeft - root._firstFarCr : root._bodyLeft; + default: + return root._firstFarCr > 0 ? root._bodyRight : root._bodyRight - root._firstSr; + } + } + y: { + switch (root.barSide) { + case "bottom": + return root._firstFarCr > 0 ? root._bodyTop - root._firstFarCr : root._bodyTop; + case "left": + return root._firstFarCr > 0 ? root._bodyTop : root._bodyTop + root._firstSr; + case "right": + return root._firstFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._firstSr; + default: + return root._firstFarCr > 0 ? root._bodyBottom + root._firstFarCr : root._bodyBottom; + } + } + } + + PathArc { + relativeX: { + switch (root.barSide) { + case "left": + return -root._firstFarCr; + case "right": + return root._firstFarCr; + default: + return -root._firstFarCr; + } + } + relativeY: { + switch (root.barSide) { + case "bottom": + return root._firstFarCr; + case "left": + return root._firstFarCr; + case "right": + return -root._firstFarCr; + default: + return -root._firstFarCr; + } + } + radiusX: root._firstFarCr + radiusY: root._firstFarCr + direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise + } + // Far edge PathLine { x: { switch (root.barSide) { case "left": - return root._bw; + return root._bodyRight; case "right": - return 0; + return root._bodyLeft; default: - return root._cr + root._sr; + return root._bodyLeft + root._secondFarInset; } } y: { switch (root.barSide) { case "bottom": - return 0; + return root._bodyTop; case "left": - return root._cr + root._bh - root._sr; + return root._bodyBottom - root._secondFarInset; case "right": - return root._cr + root._sr; + return root._bodyTop + root._secondFarInset; default: - return root._totalH; + return root._bodyBottom; + } + } + } + + // Opposite-side connector 2 + PathArc { + relativeX: { + switch (root.barSide) { + case "left": + return root._secondFarCr; + case "right": + return -root._secondFarCr; + default: + return -root._secondFarCr; + } + } + relativeY: { + switch (root.barSide) { + case "bottom": + return -root._secondFarCr; + case "left": + return root._secondFarCr; + case "right": + return -root._secondFarCr; + default: + return root._secondFarCr; + } + } + radiusX: root._secondFarCr + radiusY: root._secondFarCr + direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise + } + + PathLine { + x: { + switch (root.barSide) { + case "left": + return root._secondFarCr > 0 ? root._bodyRight : root._bodyRight; + case "right": + return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft; + default: + return root._secondFarCr > 0 ? root._bodyLeft : root._bodyLeft + root._secondSr; + } + } + y: { + switch (root.barSide) { + case "bottom": + return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop; + case "left": + return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom - root._secondSr; + case "right": + return root._secondFarCr > 0 ? root._bodyTop : root._bodyTop + root._secondSr; + default: + return root._secondFarCr > 0 ? root._bodyBottom : root._bodyBottom; } } } @@ -204,27 +332,27 @@ Item { relativeX: { switch (root.barSide) { case "left": - return -root._sr; + return -root._secondSr; case "right": - return root._sr; + return root._secondSr; default: - return -root._sr; + return -root._secondSr; } } relativeY: { switch (root.barSide) { case "bottom": - return root._sr; + return root._secondSr; case "left": - return root._sr; + return root._secondSr; case "right": - return -root._sr; + return -root._secondSr; default: - return -root._sr; + return -root._secondSr; } } - radiusX: root._sr - radiusY: root._sr + radiusX: root._secondSr + radiusY: root._secondSr direction: root.barSide === "bottom" ? PathArc.Counterclockwise : PathArc.Clockwise } @@ -233,23 +361,23 @@ Item { x: { switch (root.barSide) { case "left": - return root._cr; + return root._bodyLeft + root._ec; case "right": - return root._bw - root._cr; + return root._bodyRight - root._sc; default: - return root._cr; + return root._bodyLeft; } } y: { switch (root.barSide) { case "bottom": - return root._totalH - root._cr; + return root._bodyBottom - root._sc; case "left": - return root._cr + root._bh; + return root._bodyBottom; case "right": - return root._cr; + return root._bodyTop; default: - return root._cr; + return root._bodyTop + root._sc; } } } @@ -259,27 +387,27 @@ Item { relativeX: { switch (root.barSide) { case "left": - return -root._cr; + return -root._secondCr; case "right": - return root._cr; + return root._secondCr; default: - return -root._cr; + return -root._secondCr; } } relativeY: { switch (root.barSide) { case "bottom": - return root._cr; + return root._secondCr; case "left": - return root._cr; + return root._secondCr; case "right": - return -root._cr; + return -root._secondCr; default: - return -root._cr; + return -root._secondCr; } } - radiusX: root._cr - radiusY: root._cr + radiusX: root._secondCr + radiusY: root._secondCr direction: root.barSide === "bottom" ? PathArc.Clockwise : PathArc.Counterclockwise } } diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 8f77a44f..8eba24a3 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -198,7 +198,9 @@ Item { "bodyH": root.alignedHeight, "animX": _connectedChromeAnimX(), "animY": _connectedChromeAnimY(), - "screen": root.screen ? root.screen.name : "" + "screen": root.screen ? root.screen.name : "", + "omitStartConnector": root._closeGapOmitStartConnector(), + "omitEndConnector": root._closeGapOmitEndConnector() }; } @@ -299,6 +301,9 @@ Item { root._releaseConnectedChromeState(); } } + function onFrameCloseGapsChanged() { + root._syncPopoutChromeState(); + } } readonly property bool useBackgroundWindow: !CompositorService.isHyprland || CompositorService.useHyprlandFocusGrab @@ -421,6 +426,7 @@ Item { readonly property real screenWidth: screen ? screen.width : 0 readonly property real screenHeight: screen ? screen.height : 0 readonly property real dpr: screen ? screen.devicePixelRatio : 1 + readonly property bool closeFrameGapsActive: SettingsData.frameCloseGaps && frameOwnsConnectedChrome readonly property real frameInset: { if (!SettingsData.frameEnabled) return 0; @@ -435,6 +441,83 @@ Item { return Math.max(ft + gap, fr); } + function _popupGapValue() { + const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true; + const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4; + const rawPopupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue; + return Theme.isConnectedEffect ? 0 : rawPopupGap; + } + + function _frameEdgeInset(side) { + if (!SettingsData.frameEnabled || !root.screen) + return 0; + const edges = SettingsData.getActiveBarEdgesForScreen(root.screen); + const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness; + return Math.max(0, raw); + } + + function _edgeGapFor(side, popupGap) { + if (root.closeFrameGapsActive) + return Math.max(popupGap, _frameEdgeInset(side)); + return Math.max(popupGap, frameInset); + } + + function _sideAdjacentClearance(side) { + switch (side) { + case "left": + return adjacentBarClearance(adjacentBarInfo.leftBar); + case "right": + return adjacentBarClearance(adjacentBarInfo.rightBar); + case "top": + return adjacentBarClearance(adjacentBarInfo.topBar); + case "bottom": + return adjacentBarClearance(adjacentBarInfo.bottomBar); + default: + return 0; + } + } + + function _nearFrameBound(value, bound) { + return Math.abs(value - bound) <= Math.max(1, Theme.hairline(root.dpr) * 2); + } + + function _closeGapClampedToFrameSide(side) { + if (!root.closeFrameGapsActive) + return false; + const popupGap = _popupGapValue(); + const edgeGap = _edgeGapFor(side, popupGap); + const adjacentGap = _sideAdjacentClearance(side); + if (edgeGap < adjacentGap - Math.max(1, Theme.hairline(root.dpr) * 2)) + return false; + + switch (side) { + case "left": + return _nearFrameBound(root.alignedX, edgeGap); + case "right": + return _nearFrameBound(root.alignedX, screenWidth - popupWidth - edgeGap); + case "top": + return _nearFrameBound(root.alignedY, edgeGap); + case "bottom": + return _nearFrameBound(root.alignedY, screenHeight - popupHeight - edgeGap); + default: + return false; + } + } + + function _closeGapOmitStartConnector() { + const side = contentContainer.connectedBarSide; + if (side === "top" || side === "bottom") + return _closeGapClampedToFrameSide("left"); + return _closeGapClampedToFrameSide("top"); + } + + function _closeGapOmitEndConnector() { + const side = contentContainer.connectedBarSide; + if (side === "top" || side === "bottom") + return _closeGapClampedToFrameSide("right"); + return _closeGapClampedToFrameSide("bottom"); + } + readonly property var shadowLevel: Theme.elevationLevel3 readonly property real shadowFallbackOffset: 6 readonly property real shadowRenderPadding: (Theme.elevationEnabled && SettingsData.popoutElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, effectiveShadowDirection, shadowFallbackOffset, 8, 16) : 0 @@ -510,47 +593,43 @@ Item { } readonly property real alignedX: Theme.snap((() => { - const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true; - const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4; - const rawPopupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue; - const popupGap = Theme.isConnectedEffect ? 0 : rawPopupGap; - const edgeGap = Math.max(popupGap, frameInset); + const popupGap = _popupGapValue(); + const edgeGapLeft = _edgeGapFor("left", popupGap); + const edgeGapRight = _edgeGapFor("right", popupGap); const anchorX = Theme.isConnectedEffect ? connectedAnchorX : triggerX; switch (effectiveBarPosition) { case SettingsData.Position.Left: // bar on left: left side is bar-adjacent (popupGap), right side is frame-perpendicular (edgeGap) - return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGap, anchorX)); + return Math.max(popupGap, Math.min(screenWidth - popupWidth - edgeGapRight, anchorX)); case SettingsData.Position.Right: // bar on right: right side is bar-adjacent (popupGap), left side is frame-perpendicular (edgeGap) - return Math.max(edgeGap, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth)); + return Math.max(edgeGapLeft, Math.min(screenWidth - popupWidth - popupGap, anchorX - popupWidth)); default: const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2); - const minX = Math.max(edgeGap, adjacentBarClearance(adjacentBarInfo.leftBar)); - const maxX = screenWidth - popupWidth - Math.max(edgeGap, adjacentBarClearance(adjacentBarInfo.rightBar)); + 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)); } })(), dpr) readonly property real alignedY: Theme.snap((() => { - const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true; - const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4; - const rawPopupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue; - const popupGap = Theme.isConnectedEffect ? 0 : rawPopupGap; - const edgeGap = Math.max(popupGap, frameInset); + const popupGap = _popupGapValue(); + const edgeGapTop = _edgeGapFor("top", popupGap); + const edgeGapBottom = _edgeGapFor("bottom", popupGap); const anchorY = Theme.isConnectedEffect ? connectedAnchorY : triggerY; switch (effectiveBarPosition) { case SettingsData.Position.Bottom: // bar on bottom: bottom side is bar-adjacent (popupGap), top side is frame-perpendicular (edgeGap) - return Math.max(edgeGap, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight)); + return Math.max(edgeGapTop, Math.min(screenHeight - popupHeight - popupGap, anchorY - popupHeight)); case SettingsData.Position.Top: // bar on top: top side is bar-adjacent (popupGap), bottom side is frame-perpendicular (edgeGap) - return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGap, anchorY)); + return Math.max(popupGap, Math.min(screenHeight - popupHeight - edgeGapBottom, anchorY)); default: const rawY = triggerY - (popupHeight / 2); - const minY = Math.max(edgeGap, adjacentBarClearance(adjacentBarInfo.topBar)); - const maxY = screenHeight - popupHeight - Math.max(edgeGap, adjacentBarClearance(adjacentBarInfo.bottomBar)); + 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)); } })(), dpr)