From 582110305887732122dc62b3119f220bf75440ed Mon Sep 17 00:00:00 2001 From: purian23 Date: Fri, 1 May 2026 18:40:39 -0400 Subject: [PATCH] feat(ArcExtender): New center modal option in ConnectedMode --- quickshell/Common/SettingsData.qml | 2 + quickshell/Common/settings/SettingsSpec.js | 1 + .../DankLauncherV2/DankLauncherV2Modal.qml | 1 + .../DankLauncherV2ModalConnected.qml | 41 +++++++++++++++---- .../Modals/DankLauncherV2/LauncherContent.qml | 2 +- quickshell/Modules/Settings/FrameTab.qml | 9 ++++ 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index eb511a55..8d333f2e 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -242,6 +242,8 @@ Singleton { onFrameCloseGapsChanged: saveSettings() property string frameLauncherEmergeSide: "bottom" onFrameLauncherEmergeSideChanged: saveSettings() + property bool frameLauncherArcExtender: false + onFrameLauncherArcExtenderChanged: saveSettings() readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top" property string frameMode: "separate" onFrameModeChanged: saveSettings() diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index a65a8e3e..c5916d53 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -565,6 +565,7 @@ var SPEC = { frameBlurEnabled: { def: true }, frameCloseGaps: { def: false }, frameLauncherEmergeSide: { def: "bottom" }, + frameLauncherArcExtender: { def: false }, frameMode: { def: "separate" } }; diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml index 9e570daf..3657e948 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml @@ -22,6 +22,7 @@ Item { readonly property real modalY: impl.item ? impl.item.modalY : 0 readonly property bool frameOwnsConnectedChrome: impl.item ? (impl.item.frameOwnsConnectedChrome ?? false) : false readonly property string resolvedConnectedBarSide: impl.item ? (impl.item.resolvedConnectedBarSide ?? "") : "" + readonly property bool launcherArcExtenderActive: impl.item ? (impl.item.launcherArcExtenderActive ?? false) : false signal dialogClosed diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index c51a1333..2f081717 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -73,6 +73,7 @@ Item { readonly property string resolvedConnectedBarSide: frameConnectedMode ? preferredConnectedBarSide : "" readonly property bool frameOwnsConnectedChrome: frameConnectedMode && resolvedConnectedBarSide !== "" + readonly property bool launcherArcExtenderActive: frameOwnsConnectedChrome && SettingsData.frameLauncherArcExtender && (resolvedConnectedBarSide === "top" || resolvedConnectedBarSide === "bottom") function _dockOccupiesSide(side) { if (!SettingsData.showDock) @@ -110,10 +111,13 @@ Item { { const insetL = _frameEdgeInset("left"); const insetR = _frameEdgeInset("right"); + const insetT = _frameEdgeInset("top"); + const insetB = _frameEdgeInset("bottom"); const usable = Math.max(0, screenWidth - insetL - insetR); + const usableH = Math.max(0, screenHeight - insetT - insetB); return { "x": insetL + Math.max(0, (usable - modalWidth) / 2), - "y": resolvedConnectedBarSide === "top" ? _frameEdgeInset("top") : screenHeight - modalHeight - _frameEdgeInset("bottom") + "y": launcherArcExtenderActive ? insetT + Math.max(0, (usableH - modalHeight) / 2) : (resolvedConnectedBarSide === "top" ? insetT : screenHeight - modalHeight - insetB) }; } case "left": @@ -171,15 +175,33 @@ Item { readonly property real alignedHeight: Theme.px(modalHeight, dpr) readonly property real alignedX: Theme.snap(modalX, dpr) readonly property real alignedY: Theme.snap(modalY, dpr) + readonly property real _connectedChromeX: alignedX + readonly property real _connectedChromeY: { + if (!launcherArcExtenderActive) + return alignedY; + return resolvedConnectedBarSide === "top" ? Theme.snap(_frameEdgeInset("top"), dpr) : alignedY; + } + readonly property real _connectedChromeWidth: alignedWidth + readonly property real _connectedChromeHeight: { + if (!launcherArcExtenderActive) + return alignedHeight; + if (resolvedConnectedBarSide === "top") + return Theme.snap(Math.max(alignedHeight, alignedY + alignedHeight - _frameEdgeInset("top")), dpr); + if (resolvedConnectedBarSide === "bottom") + return Theme.snap(Math.max(alignedHeight, screenHeight - _frameEdgeInset("bottom") - alignedY), dpr); + return alignedHeight; + } // For directional/depth: window extends from screen top (content slides within) // For standard: small window tightly around the modal + shadow padding readonly property bool _needsExtendedWindow: (Theme.isDirectionalEffect && !Theme.isConnectedEffect) || Theme.isDepthEffect // Content window geometry readonly property real _cwMarginLeft: Theme.snap(alignedX - shadowPad, dpr) - readonly property real _cwMarginTop: _needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr) + readonly property real _cwMarginTop: launcherArcExtenderActive ? _connectedChromeY : (_needsExtendedWindow ? 0 : Theme.snap(alignedY - shadowPad, dpr)) readonly property real _cwWidth: alignedWidth + shadowPad * 2 readonly property real _cwHeight: { + if (launcherArcExtenderActive) + return _connectedChromeHeight; if (Theme.isDirectionalEffect && !Theme.isConnectedEffect) return screenHeight + shadowPad; if (Theme.isDepthEffect) @@ -188,7 +210,7 @@ Item { } // Where the content container sits inside the content window readonly property real _ccX: shadowPad - readonly property real _ccY: _needsExtendedWindow ? alignedY : shadowPad + readonly property real _ccY: launcherArcExtenderActive ? Theme.snap(alignedY - _cwMarginTop, dpr) : (_needsExtendedWindow ? alignedY : shadowPad) signal dialogClosed @@ -228,10 +250,10 @@ Item { ConnectedModeState.setModalState(screenName, { "visible": spotlightOpen || contentWindow.visible, "barSide": resolvedConnectedBarSide, - "bodyX": alignedX, - "bodyY": alignedY, - "bodyW": alignedWidth, - "bodyH": alignedHeight, + "bodyX": _connectedChromeX, + "bodyY": _connectedChromeY, + "bodyW": _connectedChromeWidth, + "bodyH": _connectedChromeHeight, "animX": contentContainer ? contentContainer.animX : 0, "animY": contentContainer ? contentContainer.animY : 0, "omitStartConnector": false, @@ -304,7 +326,7 @@ Item { const screenName = _currentScreenName(); if (!screenName) return; - ConnectedModeState.setModalBody(screenName, alignedX, alignedY, alignedWidth, alignedHeight); + ConnectedModeState.setModalBody(screenName, _connectedChromeX, _connectedChromeY, _connectedChromeWidth, _connectedChromeHeight); } function _releaseModalChrome() { @@ -318,6 +340,7 @@ Item { } onFrameOwnsConnectedChromeChanged: _syncModalChromeState() + onLauncherArcExtenderActiveChanged: _queueFullSync() onResolvedConnectedBarSideChanged: _queueFullSync() onSpotlightOpenChanged: _queueFullSync() onAlignedXChanged: _queueBodySync() @@ -705,7 +728,7 @@ Item { readonly property bool directionalEffect: Theme.isDirectionalEffect readonly property bool depthEffect: Theme.isDepthEffect readonly property real _connectedTravelX: Math.max(Theme.effectAnimOffset, root.alignedWidth + Theme.spacingL) - readonly property real _connectedTravelY: Math.max(Theme.effectAnimOffset, root.alignedHeight + Theme.spacingL) + readonly property real _connectedTravelY: root.launcherArcExtenderActive ? root._connectedChromeHeight : Math.max(Theme.effectAnimOffset, root.alignedHeight + Theme.spacingL) readonly property real collapsedMotionX: { if (root.frameOwnsConnectedChrome) { switch (root.resolvedConnectedBarSide) { diff --git a/quickshell/Modals/DankLauncherV2/LauncherContent.qml b/quickshell/Modals/DankLauncherV2/LauncherContent.qml index 563b1fdc..f1fd775c 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContent.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContent.qml @@ -281,7 +281,7 @@ FocusScope { anchors.rightMargin: root.parentModal?.borderWidth ?? 1 anchors.bottomMargin: root.parentModal?.borderWidth ?? 1 readonly property bool showFooter: SettingsData.dankLauncherV2Size !== "micro" && SettingsData.dankLauncherV2ShowFooter - readonly property bool _connectedArcAtFooter: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "bottom") + readonly property bool _connectedArcAtFooter: (root.parentModal?.frameOwnsConnectedChrome ?? false) && (root.parentModal?.resolvedConnectedBarSide === "bottom") && !(root.parentModal?.launcherArcExtenderActive ?? false) height: showFooter ? (_connectedArcAtFooter ? 76 : 36) : 0 visible: showFooter clip: true diff --git a/quickshell/Modules/Settings/FrameTab.qml b/quickshell/Modules/Settings/FrameTab.qml index ca74e2f0..d578766a 100644 --- a/quickshell/Modules/Settings/FrameTab.qml +++ b/quickshell/Modules/Settings/FrameTab.qml @@ -321,6 +321,15 @@ Item { SettingsData.set("frameLauncherEmergeSide", index === 1 ? "top" : "bottom"); } } + + SettingsToggleRow { + settingKey: "frameLauncherArcExtender" + tags: ["frame", "connected", "launcher", "arc", "extender", "center"] + text: I18n.tr("Arc Extender") + description: I18n.tr("Extend the launcher surface to the screen center") + checked: SettingsData.frameLauncherArcExtender + onToggled: checked => SettingsData.set("frameLauncherArcExtender", checked) + } } SettingsCard {