From 2261883426ff6f4d3f037a5cf7775bfba9f7fa68 Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 6 Apr 2026 17:48:59 -0400 Subject: [PATCH] (frameMode): Restore user settings when exiting frame mode - Align blur settings in non-FrameMode motion settings --- quickshell/Common/SettingsData.qml | 174 +++++++++++++++++- quickshell/Common/settings/SettingsSpec.js | 1 + quickshell/Modules/Settings/DankBarTab.qml | 34 ++++ quickshell/Modules/Settings/FrameTab.qml | 2 +- .../Modules/Settings/TypographyMotionTab.qml | 2 +- quickshell/Widgets/DankPopout.qml | 16 +- 6 files changed, 217 insertions(+), 12 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 788c20d8..cdae84b1 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -237,6 +237,21 @@ Singleton { onFrameBlurEnabledChanged: saveSettings() property int previousDirectionalMode: 1 onPreviousDirectionalModeChanged: saveSettings() + property var connectedFrameBarStyleBackups: ({}) + onConnectedFrameBarStyleBackupsChanged: saveSettings() + readonly property bool connectedFrameModeActive: frameEnabled + && motionEffect === SettingsData.AnimationEffect.Directional + && directionalAnimationMode === 3 + onConnectedFrameModeActiveChanged: { + if (_loading) + return; + if (connectedFrameModeActive) { + _captureConnectedFrameBarStyleBackups(barConfigs, true); + _enforceConnectedModeBarStyleReset(); + } else { + _restoreConnectedFrameBarStyleBackups(); + } + } readonly property color effectiveFrameColor: { const fc = frameColor; @@ -1331,6 +1346,7 @@ Singleton { _loading = false; } loadPluginSettings(); + Qt.callLater(() => _reconcileConnectedFrameBarStyles()); } property var _pendingMigration: null @@ -1444,6 +1460,149 @@ Singleton { pluginSettingsFile.setText(JSON.stringify(pluginSettings, null, 2)); } + function _connectedFrameBarStyleSnapshot(config) { + return { + "shadowIntensity": config?.shadowIntensity ?? 0, + "squareCorners": config?.squareCorners ?? false, + "gothCornersEnabled": config?.gothCornersEnabled ?? false, + "borderEnabled": config?.borderEnabled ?? false + }; + } + + function _hasConnectedFrameBarStyleBackups() { + return connectedFrameBarStyleBackups && Object.keys(connectedFrameBarStyleBackups).length > 0; + } + + function _captureConnectedFrameBarStyleBackups(configs, overwriteExisting) { + if (!Array.isArray(configs)) + return; + + const nextBackups = JSON.parse(JSON.stringify(connectedFrameBarStyleBackups || {})); + const validIds = {}; + let changed = false; + + for (let i = 0; i < configs.length; i++) { + const config = configs[i]; + if (!config?.id) + continue; + validIds[config.id] = true; + + if (!overwriteExisting && nextBackups[config.id] !== undefined) + continue; + + const snapshot = _connectedFrameBarStyleSnapshot(config); + if (JSON.stringify(nextBackups[config.id]) !== JSON.stringify(snapshot)) { + nextBackups[config.id] = snapshot; + changed = true; + } + } + + if (overwriteExisting) { + for (const barId in nextBackups) { + if (validIds[barId]) + continue; + delete nextBackups[barId]; + changed = true; + } + } + + if (changed) + connectedFrameBarStyleBackups = nextBackups; + } + + function _restoreConnectedFrameBarStyleBackups() { + if (!_hasConnectedFrameBarStyleBackups()) + return; + + const backups = connectedFrameBarStyleBackups || {}; + const configs = JSON.parse(JSON.stringify(barConfigs)); + let changed = false; + + for (let i = 0; i < configs.length; i++) { + const backup = backups[configs[i].id]; + if (!backup) + continue; + for (const key in backup) { + if (configs[i][key] === backup[key]) + continue; + configs[i][key] = backup[key]; + changed = true; + } + } + + if (changed) + barConfigs = configs; + connectedFrameBarStyleBackups = ({}); + if (changed) + updateBarConfigs(); + } + + function _reconcileConnectedFrameBarStyles() { + if (connectedFrameModeActive) { + if (!_hasConnectedFrameBarStyleBackups()) + _captureConnectedFrameBarStyleBackups(barConfigs, true); + _enforceConnectedModeBarStyleReset(); + return; + } + _restoreConnectedFrameBarStyleBackups(); + } + + function _sanitizeBarConfigForConnectedFrame(config) { + if (!connectedFrameModeActive || !config) + return config; + + let changed = false; + const sanitized = Object.assign({}, config); + + if ((sanitized.shadowIntensity ?? 0) !== 0) { + sanitized.shadowIntensity = 0; + changed = true; + } + if (sanitized.squareCorners ?? false) { + sanitized.squareCorners = false; + changed = true; + } + if (sanitized.gothCornersEnabled ?? false) { + sanitized.gothCornersEnabled = false; + changed = true; + } + if (sanitized.borderEnabled ?? false) { + sanitized.borderEnabled = false; + changed = true; + } + + return changed ? sanitized : config; + } + + function _sanitizeBarConfigsForConnectedFrame(configs) { + if (!connectedFrameModeActive || !Array.isArray(configs)) + return { + "configs": configs, + "changed": false + }; + + let changed = false; + const sanitizedConfigs = configs.map(config => { + const sanitized = _sanitizeBarConfigForConnectedFrame(config); + if (sanitized !== config) + changed = true; + return sanitized; + }); + + return { + "configs": changed ? sanitizedConfigs : configs, + "changed": changed + }; + } + + function _enforceConnectedModeBarStyleReset() { + const result = _sanitizeBarConfigsForConnectedFrame(barConfigs); + if (!result.changed) + return; + barConfigs = result.configs; + updateBarConfigs(); + } + function detectAvailableIconThemes() { const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") || ""; const localData = Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation)); @@ -1591,7 +1750,7 @@ Singleton { const spacing = barSpacing !== undefined ? barSpacing : (defaultBar?.spacing ?? 4); const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top); const rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0); - const isConnected = frameEnabled && motionEffect === 1 && directionalAnimationMode === 3; + const isConnected = connectedFrameModeActive; const bottomGap = isConnected ? 0 : Math.max(0, rawBottomGap); const useAutoGaps = (barConfig && barConfig.popupGapsAuto !== undefined) ? barConfig.popupGapsAuto : (defaultBar?.popupGapsAuto ?? true); @@ -1715,7 +1874,7 @@ Singleton { const screenWidth = screen.width; const screenHeight = screen.height; const position = barPosition !== undefined ? barPosition : (defaultBar?.position ?? SettingsData.Position.Top); - const isConnected = frameEnabled && motionEffect === 1 && directionalAnimationMode === 3; + const isConnected = connectedFrameModeActive; const rawBottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : (defaultBar?.bottomGap ?? 0)) : (defaultBar?.bottomGap ?? 0); const bottomGap = isConnected ? 0 : rawBottomGap; @@ -1830,7 +1989,9 @@ Singleton { function addBarConfig(config) { const configs = JSON.parse(JSON.stringify(barConfigs)); configs.push(config); - barConfigs = configs; + if (connectedFrameModeActive) + _captureConnectedFrameBarStyleBackups(configs, false); + barConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs; updateBarConfigs(); } @@ -1842,7 +2003,7 @@ Singleton { const positionChanged = updates.position !== undefined && configs[index].position !== updates.position; Object.assign(configs[index], updates); - barConfigs = configs; + barConfigs = _sanitizeBarConfigsForConnectedFrame(configs).configs; updateBarConfigs(); if (positionChanged) { @@ -1896,6 +2057,11 @@ Singleton { return; const configs = barConfigs.filter(cfg => cfg.id !== barId); barConfigs = configs; + if (connectedFrameBarStyleBackups?.[barId] !== undefined) { + const nextBackups = JSON.parse(JSON.stringify(connectedFrameBarStyleBackups || {})); + delete nextBackups[barId]; + connectedFrameBarStyleBackups = nextBackups; + } updateBarConfigs(); } diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index a0ad69c4..d69529df 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -445,6 +445,7 @@ var SPEC = { displayProfileAutoSelect: { def: false }, displayShowDisconnected: { def: false }, displaySnapToEdge: { def: true }, + connectedFrameBarStyleBackups: { def: {} }, barConfigs: { def: [{ diff --git a/quickshell/Modules/Settings/DankBarTab.qml b/quickshell/Modules/Settings/DankBarTab.qml index df0e1e4c..726664c3 100644 --- a/quickshell/Modules/Settings/DankBarTab.qml +++ b/quickshell/Modules/Settings/DankBarTab.qml @@ -27,6 +27,7 @@ Item { const pos = selectedBarConfig?.position ?? SettingsData.Position.Top; return pos === SettingsData.Position.Left || pos === SettingsData.Position.Right; } + readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive Timer { id: horizontalBarChangeDebounce @@ -1110,6 +1111,35 @@ Item { } } + Item { + visible: dankBarTab.connectedFrameModeActive + width: parent.width + implicitHeight: connectedFrameStyleNote.implicitHeight + Theme.spacingS * 2 + + Row { + id: connectedFrameStyleNote + x: Theme.spacingM + width: parent.width - Theme.spacingM * 2 + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingS + + DankIcon { + name: "frame_source" + size: Theme.fontSizeMedium + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: I18n.tr("Connected Frame mode keeps bar shadow override, border, and corner overrides off while active") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + wrapMode: Text.WordWrap + width: parent.width - Theme.fontSizeMedium - Theme.spacingS + } + } + } + SettingsCard { id: shadowCard iconName: "layers" @@ -1118,6 +1148,8 @@ Item { collapsible: true expanded: true visible: selectedBarConfig?.enabled + enabled: !dankBarTab.connectedFrameModeActive + opacity: dankBarTab.connectedFrameModeActive ? 0.5 : 1.0 readonly property bool shadowActive: (selectedBarConfig?.shadowIntensity ?? 0) > 0 readonly property bool isCustomColor: (selectedBarConfig?.shadowColorMode ?? "default") === "custom" @@ -1451,6 +1483,8 @@ Item { iconName: "border_style" title: I18n.tr("Border") visible: selectedBarConfig?.enabled + enabled: !dankBarTab.connectedFrameModeActive + opacity: dankBarTab.connectedFrameModeActive ? 0.5 : 1.0 checked: selectedBarConfig?.borderEnabled ?? false onToggled: checked => SettingsData.updateBarConfig(selectedBarId, { borderEnabled: checked diff --git a/quickshell/Modules/Settings/FrameTab.qml b/quickshell/Modules/Settings/FrameTab.qml index 4fcb7e74..95066a8b 100644 --- a/quickshell/Modules/Settings/FrameTab.qml +++ b/quickshell/Modules/Settings/FrameTab.qml @@ -281,7 +281,7 @@ Item { 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)") - checked: SettingsData.motionEffect === 1 && SettingsData.directionalAnimationMode === 3 + checked: SettingsData.connectedFrameModeActive onToggled: checked => { if (checked) { if (SettingsData.directionalAnimationMode !== 3) diff --git a/quickshell/Modules/Settings/TypographyMotionTab.qml b/quickshell/Modules/Settings/TypographyMotionTab.qml index 1e53dd5a..9dc0841b 100644 --- a/quickshell/Modules/Settings/TypographyMotionTab.qml +++ b/quickshell/Modules/Settings/TypographyMotionTab.qml @@ -207,7 +207,7 @@ Item { settingKey: "directionalAnimationMode" text: I18n.tr("Directional Behavior") description: { - if (SettingsData.directionalAnimationMode === 3 && SettingsData.frameEnabled) + if (SettingsData.connectedFrameModeActive) return I18n.tr("Popouts emerge flush from the bar edge as a single continuous piece, with corner connectors bridging the junction"); return I18n.tr("How the popout emerges from the DankBar"); } diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 0dd70d78..494a5c2b 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -539,9 +539,13 @@ Item { blurEnabled: root.effectiveSurfaceBlurEnabled readonly property real s: Math.min(1, contentContainer.scaleValue) + readonly property bool trackBlurFromBarEdge: Theme.isConnectedEffect + || (typeof SettingsData !== "undefined" + && Theme.isDirectionalEffect + && SettingsData.directionalAnimationMode !== 2) - // Connected mode: clamp animY/animX to popup bounds so blur grows from the - // bar edge in sync with the slide-in animation, never entering the bar area. + // Directional popouts clip to the bar edge, so the blur needs to grow from + // that same edge instead of translating through the bar before settling. readonly property real _dyClamp: (contentContainer.barTop || contentContainer.barBottom) ? Math.max(-contentContainer.height, Math.min(contentContainer.animY, contentContainer.height)) : 0 @@ -549,23 +553,23 @@ Item { ? Math.max(-contentContainer.width, Math.min(contentContainer.animX, contentContainer.width)) : 0 - blurX: Theme.isConnectedEffect + blurX: trackBlurFromBarEdge ? contentContainer.x + (contentContainer.barRight ? _dxClamp : 0) : contentContainer.x + contentContainer.width * (1 - s) * 0.5 + Theme.snap(contentContainer.animX, root.dpr) - contentContainer.horizontalConnectorExtent * s - blurY: Theme.isConnectedEffect + blurY: trackBlurFromBarEdge ? contentContainer.y + (contentContainer.barBottom ? _dyClamp : 0) : contentContainer.y + contentContainer.height * (1 - s) * 0.5 + Theme.snap(contentContainer.animY, root.dpr) - contentContainer.verticalConnectorExtent * s blurWidth: (shouldBeVisible && contentWrapper.opacity > 0) - ? (Theme.isConnectedEffect + ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.width - Math.abs(_dxClamp)) : (contentContainer.width + contentContainer.horizontalConnectorExtent * 2) * s) : 0 blurHeight: (shouldBeVisible && contentWrapper.opacity > 0) - ? (Theme.isConnectedEffect + ? (trackBlurFromBarEdge ? Math.max(0, contentContainer.height - Math.abs(_dyClamp)) : (contentContainer.height + contentContainer.verticalConnectorExtent * 2) * s) : 0