From 19c561da141ddb185331d98ed56b542ab0805cea Mon Sep 17 00:00:00 2001 From: purian23 Date: Mon, 4 May 2026 10:43:07 -0400 Subject: [PATCH] refactor: (Framemode) Added DeferredAction for dbar/dock state handling --- quickshell/Common/DeferredAction.qml | 55 +++++++ quickshell/DMSShell.qml | 30 +++- quickshell/Modules/DankBar/DankBarContent.qml | 155 +++++++++++++----- quickshell/Modules/DankBar/DankBarWindow.qml | 11 -- quickshell/Modules/Dock/Dock.qml | 28 ++-- quickshell/Modules/Frame/FrameWindow.qml | 19 ++- quickshell/Services/BarWidgetService.qml | 31 ++-- 7 files changed, 245 insertions(+), 84 deletions(-) create mode 100644 quickshell/Common/DeferredAction.qml diff --git a/quickshell/Common/DeferredAction.qml b/quickshell/Common/DeferredAction.qml new file mode 100644 index 00000000..b91d3f0b --- /dev/null +++ b/quickshell/Common/DeferredAction.qml @@ -0,0 +1,55 @@ +import QtQuick + +Item { + id: root + + visible: false + width: 0 + height: 0 + + property int interval: 0 + property bool pending: false + + signal triggered + + function schedule() { + if (!root.enabled || root.pending) + return; + root.pending = true; + deferTimer.restart(); + } + + function restart() { + if (!root.enabled) + return; + root.pending = true; + deferTimer.restart(); + } + + function flush() { + if (!root.pending) + return; + deferTimer.stop(); + root.pending = false; + root.triggered(); + } + + function cancel() { + deferTimer.stop(); + root.pending = false; + } + + onEnabledChanged: { + if (!enabled) + cancel(); + } + + Timer { + id: deferTimer + interval: root.interval + repeat: false + onTriggered: root.flush() + } + + Component.onDestruction: cancel() +} diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 55a01de3..64f93f9e 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -164,7 +164,22 @@ Item { } } + property bool barSurfacesLoaded: true + + function recreateBarSurfaces() { + if (barSurfacesLoaded) + barSurfacesLoaded = false; + barSurfaceReloadAction.schedule(); + } + + DeferredAction { + id: barSurfaceReloadAction + onTriggered: root.barSurfacesLoaded = true + } + property string _barLayoutStateJson: { + if (!barSurfacesLoaded) + return "[]"; const configs = SettingsData.barConfigs; const mapped = configs.map(c => ({ id: c.id, @@ -188,6 +203,19 @@ Item { } } + Connections { + target: SettingsData + function onFrameEnabledChanged() { + root.recreateBarSurfaces(); + } + function onConnectedFrameModeActiveChanged() { + root.recreateBarSurfaces(); + } + function onForceDankBarLayoutRefresh() { + root.recreateBarSurfaces(); + } + } + Frame {} Repeater { @@ -203,7 +231,7 @@ Item { id: barLoader required property var modelData property var barConfig: SettingsData.barConfigs.find(cfg => cfg.id === modelData.id) || null - active: barConfig?.enabled ?? false + active: root.barSurfacesLoaded && (barConfig?.enabled ?? false) asynchronous: false sourceComponent: DankBar { diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index 2b8ca82b..b0df7747 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -19,33 +19,52 @@ Item { property var leftWidgetsModel property var centerWidgetsModel property var rightWidgetsModel + property bool _animateFrameInsets: false readonly property real innerPadding: barConfig?.innerPadding ?? 4 readonly property real outlineThickness: (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0 + readonly property real _edgeBaseMargin: Math.max(Theme.spacingXS, innerPadding * 0.8) + readonly property real _frameEdgeFloorInset: SettingsData.frameEnabled ? Math.max(0, SettingsData.frameThickness - _edgeBaseMargin) : 0 + readonly property bool _hasBarWindow: barWindow !== undefined && barWindow !== null + readonly property bool _barIsVertical: _hasBarWindow ? barWindow.isVertical : false + readonly property string _barScreenName: _hasBarWindow ? (barWindow.screenName || "") : "" + readonly property bool hasAdjacentTopBarLive: _hasBarWindow && barWindow.hasAdjacentTopBar + readonly property bool hasAdjacentBottomBarLive: _hasBarWindow && barWindow.hasAdjacentBottomBar + readonly property bool hasAdjacentLeftBarLive: _hasBarWindow && barWindow.hasAdjacentLeftBar + readonly property bool hasAdjacentRightBarLive: _hasBarWindow && barWindow.hasAdjacentRightBar + property bool _hadAdjacentTopBar: false + property bool _hadAdjacentBottomBar: false + property bool _hadAdjacentLeftBar: false + property bool _hadAdjacentRightBar: false + + onHasAdjacentTopBarLiveChanged: if (hasAdjacentTopBarLive) _hadAdjacentTopBar = true + onHasAdjacentBottomBarLiveChanged: if (hasAdjacentBottomBarLive) _hadAdjacentBottomBar = true + onHasAdjacentLeftBarLiveChanged: if (hasAdjacentLeftBarLive) _hadAdjacentLeftBar = true + onHasAdjacentRightBarLiveChanged: if (hasAdjacentRightBarLive) _hadAdjacentRightBar = true readonly property real _frameLeftInset: { - if (!SettingsData.frameEnabled || barWindow.isVertical) return 0 - return barWindow.hasAdjacentLeftBar + if (!_hasBarWindow || !SettingsData.frameEnabled || _barIsVertical) return 0 + return hasAdjacentLeftBarLive ? SettingsData.frameBarSize - : 0 + : (_hadAdjacentLeftBar ? _frameEdgeFloorInset : 0) } readonly property real _frameRightInset: { - if (!SettingsData.frameEnabled || barWindow.isVertical) return 0 - return barWindow.hasAdjacentRightBar + if (!_hasBarWindow || !SettingsData.frameEnabled || _barIsVertical) return 0 + return hasAdjacentRightBarLive ? SettingsData.frameBarSize - : 0 + : (_hadAdjacentRightBar ? _frameEdgeFloorInset : 0) } readonly property real _frameTopInset: { - if (!SettingsData.frameEnabled || !barWindow.isVertical) return 0 - return barWindow.hasAdjacentTopBar + if (!_hasBarWindow || !SettingsData.frameEnabled || !_barIsVertical) return 0 + return hasAdjacentTopBarLive ? SettingsData.frameThickness - : 0 + : (_hadAdjacentTopBar ? _frameEdgeFloorInset : 0) } readonly property real _frameBottomInset: { - if (!SettingsData.frameEnabled || !barWindow.isVertical) return 0 - return barWindow.hasAdjacentBottomBar + if (!_hasBarWindow || !SettingsData.frameEnabled || !_barIsVertical) return 0 + return hasAdjacentBottomBarLive ? SettingsData.frameThickness - : 0 + : (_hadAdjacentBottomBar ? _frameEdgeFloorInset : 0) } property alias hLeftSection: hLeftSection @@ -56,16 +75,61 @@ Item { property alias vRightSection: vRightSection anchors.fill: parent - anchors.leftMargin: Math.max(Theme.spacingXS, innerPadding * 0.8) + _frameLeftInset - anchors.rightMargin: Math.max(Theme.spacingXS, innerPadding * 0.8) + _frameRightInset - anchors.topMargin: (barWindow.isVertical - ? (barWindow.hasAdjacentTopBar ? outlineThickness : Theme.spacingXS) + anchors.leftMargin: _edgeBaseMargin + _frameLeftInset + anchors.rightMargin: _edgeBaseMargin + _frameRightInset + anchors.topMargin: (_barIsVertical + ? (hasAdjacentTopBarLive ? outlineThickness : Theme.spacingXS) : 0) + _frameTopInset - anchors.bottomMargin: (barWindow.isVertical - ? (barWindow.hasAdjacentBottomBar ? outlineThickness : Theme.spacingXS) + anchors.bottomMargin: (_barIsVertical + ? (hasAdjacentBottomBarLive ? outlineThickness : Theme.spacingXS) : 0) + _frameBottomInset clip: false + DeferredAction { + id: enableFrameInsetAnimation + onTriggered: topBarContent._animateFrameInsets = true + } + + Component.onCompleted: { + _hadAdjacentTopBar = hasAdjacentTopBarLive; + _hadAdjacentBottomBar = hasAdjacentBottomBarLive; + _hadAdjacentLeftBar = hasAdjacentLeftBarLive; + _hadAdjacentRightBar = hasAdjacentRightBarLive; + enableFrameInsetAnimation.schedule(); + } + + Behavior on anchors.leftMargin { + enabled: _animateFrameInsets && SettingsData.frameEnabled + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + + Behavior on anchors.rightMargin { + enabled: _animateFrameInsets && SettingsData.frameEnabled + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + + Behavior on anchors.topMargin { + enabled: _animateFrameInsets && SettingsData.frameEnabled + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + + Behavior on anchors.bottomMargin { + enabled: _animateFrameInsets && SettingsData.frameEnabled + NumberAnimation { + duration: Theme.shortDuration + easing.type: Easing.OutCubic + } + } + property int componentMapRevision: 0 function updateComponentMap() { @@ -73,10 +137,14 @@ Item { } readonly property var sortedToplevels: { - return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, barWindow.screenName); + if (!_hasBarWindow) { + return []; + } + return CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, _barScreenName); } function getRealWorkspaces() { + const screenName = _barScreenName; if (CompositorService.isNiri) { const fallbackWorkspaces = [ { @@ -90,16 +158,16 @@ Item { "name": "" } ]; - if (!barWindow.screenName || SettingsData.workspaceFollowFocus) { + if (!screenName || SettingsData.workspaceFollowFocus) { const currentWorkspaces = NiriService.getCurrentOutputWorkspaces(); return currentWorkspaces.length > 0 ? currentWorkspaces : fallbackWorkspaces; } - const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === barWindow.screenName); + const workspaces = NiriService.allWorkspaces.filter(ws => ws.output === screenName); return workspaces.length > 0 ? workspaces : fallbackWorkspaces; } else if (CompositorService.isHyprland) { const workspaces = Hyprland.workspaces?.values || []; - if (!barWindow.screenName || SettingsData.workspaceFollowFocus) { + if (!screenName || SettingsData.workspaceFollowFocus) { const sorted = workspaces.slice().sort((a, b) => a.id - b.id); const filtered = sorted.filter(ws => ws.id > -1); return filtered.length > 0 ? filtered : [ @@ -111,7 +179,7 @@ Item { } const monitorWorkspaces = workspaces.filter(ws => { - return ws.lastIpcObject && ws.lastIpcObject.monitor === barWindow.screenName && ws.id > -1; + return ws.lastIpcObject && ws.lastIpcObject.monitor === screenName && ws.id > -1; }); if (monitorWorkspaces.length === 0) { @@ -133,7 +201,7 @@ Item { length: DwlService.tagCount }, (_, i) => i); } - return DwlService.getVisibleTags(barWindow.screenName); + return DwlService.getVisibleTags(screenName); } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const workspaces = I3.workspaces?.values || []; if (workspaces.length === 0) @@ -143,11 +211,11 @@ Item { } ]; - if (!barWindow.screenName || SettingsData.workspaceFollowFocus) { + if (!screenName || SettingsData.workspaceFollowFocus) { return workspaces.slice().sort((a, b) => a.num - b.num); } - const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === barWindow.screenName); + const monitorWorkspaces = workspaces.filter(ws => ws.monitor?.name === screenName); return monitorWorkspaces.length > 0 ? monitorWorkspaces.sort((a, b) => a.num - b.num) : [ { "num": 1 @@ -158,31 +226,32 @@ Item { } function getCurrentWorkspace() { + const screenName = _barScreenName; if (CompositorService.isNiri) { - if (!barWindow.screenName || SettingsData.workspaceFollowFocus) { + if (!screenName || SettingsData.workspaceFollowFocus) { return NiriService.getCurrentWorkspaceNumber(); } - const activeWs = NiriService.allWorkspaces.find(ws => ws.output === barWindow.screenName && ws.is_active); + const activeWs = NiriService.allWorkspaces.find(ws => ws.output === screenName && ws.is_active); return activeWs ? activeWs.idx : 1; } else if (CompositorService.isHyprland) { const monitors = Hyprland.monitors?.values || []; - const currentMonitor = monitors.find(monitor => monitor.name === barWindow.screenName); + const currentMonitor = monitors.find(monitor => monitor.name === screenName); return currentMonitor?.activeWorkspace?.id ?? 1; } else if (CompositorService.isDwl) { if (!DwlService.dwlAvailable) return 0; - const outputState = DwlService.getOutputState(barWindow.screenName); + const outputState = DwlService.getOutputState(screenName); if (!outputState || !outputState.tags) return 0; - const activeTags = DwlService.getActiveTags(barWindow.screenName); + const activeTags = DwlService.getActiveTags(screenName); return activeTags.length > 0 ? activeTags[0] : 0; } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { - if (!barWindow.screenName || SettingsData.workspaceFollowFocus) { + if (!screenName || SettingsData.workspaceFollowFocus) { const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); return focusedWs ? focusedWs.num : 1; } - const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === barWindow.screenName && ws.focused === true); + const focusedWs = I3.workspaces?.values?.find(ws => ws.monitor?.name === screenName && ws.focused === true); return focusedWs ? focusedWs.num : 1; } return 1; @@ -223,7 +292,7 @@ Item { const nextIndex = direction > 0 ? Math.min(validIndex + 1, realWorkspaces.length - 1) : Math.max(validIndex - 1, 0); if (nextIndex !== validIndex) { - DwlService.switchToTag(barWindow.screenName, realWorkspaces[nextIndex]); + DwlService.switchToTag(_barScreenName, realWorkspaces[nextIndex]); } } else if (CompositorService.isSway || CompositorService.isScroll || CompositorService.isMiracle) { const currentWs = getCurrentWorkspace(); @@ -290,8 +359,8 @@ Item { readonly property int leftToMediaGap: mediaMaxWidth > 0 ? Math.max(0, mediaLeftEdge - leftSectionRightEdge) : leftToClockGap readonly property int mediaToClockGap: mediaMaxWidth > 0 ? Theme.spacingS : 0 readonly property int clockToRightGap: validLayout ? Math.max(0, rightSectionLeftEdge - clockRightEdge) : 1000 - readonly property bool spacingTight: !barWindow.isVertical && validLayout && (leftToMediaGap < 150 || clockToRightGap < 100) - readonly property bool overlapping: !barWindow.isVertical && validLayout && (leftToMediaGap < 100 || clockToRightGap < 50) + readonly property bool spacingTight: !_barIsVertical && validLayout && (leftToMediaGap < 150 || clockToRightGap < 100) + readonly property bool overlapping: !_barIsVertical && validLayout && (leftToMediaGap < 100 || clockToRightGap < 50) function getWidgetEnabled(enabled) { return enabled !== false; @@ -761,7 +830,7 @@ Item { WorkspaceSwitcher { axis: barWindow.axis - screenName: barWindow.screenName + screenName: _barScreenName widgetHeight: barWindow.widgetThickness barThickness: barWindow.effectiveBarThickness parentScreen: barWindow.screen @@ -1378,8 +1447,8 @@ Item { id: spacerComponent Item { - width: barWindow.isVertical ? barWindow.widgetThickness : (parent.spacerSize || 20) - height: barWindow.isVertical ? (parent.spacerSize || 20) : barWindow.widgetThickness + width: _barIsVertical ? barWindow.widgetThickness : (parent.spacerSize || 20) + height: _barIsVertical ? (parent.spacerSize || 20) : barWindow.widgetThickness implicitWidth: width implicitHeight: height @@ -1408,14 +1477,14 @@ Item { id: separatorComponent Item { - width: barWindow.isVertical ? parent.barThickness : 1 - height: barWindow.isVertical ? 1 : parent.barThickness + width: _barIsVertical ? parent.barThickness : 1 + height: _barIsVertical ? 1 : parent.barThickness implicitWidth: width implicitHeight: height Rectangle { - width: barWindow.isVertical ? parent.width * 0.6 : 1 - height: barWindow.isVertical ? 1 : parent.height * 0.6 + width: _barIsVertical ? parent.width * 0.6 : 1 + height: _barIsVertical ? 1 : parent.height * 0.6 anchors.centerIn: parent color: Theme.outline opacity: 0.3 diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 14722c09..cc9aef3f 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -510,17 +510,6 @@ PanelWindow { property var nativeInhibitor: null Component.onCompleted: { - if (SettingsData.forceStatusBarLayoutRefresh) { - SettingsData.forceStatusBarLayoutRefresh.connect(() => { - Qt.callLater(() => { - stackContainer.visible = false; - Qt.callLater(() => { - stackContainer.visible = true; - }); - }); - }); - } - updateGpuTempConfig(); _updateBackgroundAlpha(); _updateHasMaximizedToplevel(); diff --git a/quickshell/Modules/Dock/Dock.qml b/quickshell/Modules/Dock/Dock.qml index fe152ef9..ab4ed229 100644 --- a/quickshell/Modules/Dock/Dock.qml +++ b/quickshell/Modules/Dock/Dock.qml @@ -202,18 +202,21 @@ Variants { ConnectedModeState.setDockSlide(dock._dockScreenName, dockSlide.x, dockSlide.y); } - property bool _slideSyncPending: false + DeferredAction { + id: dockSlideSync + enabled: SettingsData.connectedFrameModeActive + onTriggered: dock._syncDockSlide() + } + function _queueSlideSync() { if (!SettingsData.connectedFrameModeActive) return; - if (_slideSyncPending) - return; - _slideSyncPending = true; - Qt.callLater(dock._flushSlideSync); + dockSlideSync.schedule(); } - function _flushSlideSync() { - _slideSyncPending = false; - dock._syncDockSlide(); + + DeferredAction { + id: dockChromeSync + onTriggered: dock._syncDockChromeState() } property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData) @@ -390,8 +393,12 @@ Variants { } } - Component.onCompleted: Qt.callLater(() => dock._syncDockChromeState()) - Component.onDestruction: ConnectedModeState.clearDockState(dock._dockScreenName) + Component.onCompleted: dockChromeSync.schedule() + Component.onDestruction: { + dockChromeSync.cancel(); + dockSlideSync.cancel(); + ConnectedModeState.clearDockState(dock._dockScreenName); + } onRevealChanged: dock._syncDockChromeState() onWidthChanged: dock._syncDockChromeState() @@ -404,6 +411,7 @@ Variants { Connections { target: SettingsData function onConnectedFrameModeActiveChanged() { + dockSlideSync.cancel(); dock._syncDockChromeState(); } } diff --git a/quickshell/Modules/Frame/FrameWindow.qml b/quickshell/Modules/Frame/FrameWindow.qml index 65ae1f83..3e6195cf 100644 --- a/quickshell/Modules/Frame/FrameWindow.qml +++ b/quickshell/Modules/Frame/FrameWindow.qml @@ -1195,16 +1195,16 @@ PanelWindow { // Coalesce bursts of settings-change signals into a single _buildBlur() call // on the next event loop tick. - property bool _blurRebuildPending: false + DeferredAction { + id: blurRebuildAction + onTriggered: win._runBlurRebuild() + } + function _scheduleBlurRebuild() { - if (_blurRebuildPending) - return; - _blurRebuildPending = true; - Qt.callLater(_runBlurRebuild); + blurRebuildAction.schedule(); } function _runBlurRebuild() { - _blurRebuildPending = false; - win._buildBlur(); + _buildBlur(); } Connections { @@ -1257,7 +1257,10 @@ PanelWindow { } Component.onCompleted: win._scheduleBlurRebuild() - Component.onDestruction: win._teardownBlur() + Component.onDestruction: { + blurRebuildAction.cancel(); + win._teardownBlur(); + } FrameBorder { anchors.fill: parent diff --git a/quickshell/Services/BarWidgetService.qml b/quickshell/Services/BarWidgetService.qml index 0ae2450a..66dc4e51 100644 --- a/quickshell/Services/BarWidgetService.qml +++ b/quickshell/Services/BarWidgetService.qml @@ -18,13 +18,12 @@ Singleton { function registerWidget(widgetId, screenName, widgetRef) { if (!widgetId || !screenName || !widgetRef) return; - if (typeof widgetRegistry !== "object" || widgetRegistry === null) - widgetRegistry = ({}); - if (!widgetRegistry[widgetId]) - widgetRegistry[widgetId] = {}; - - widgetRegistry[widgetId][screenName] = widgetRef; + const nextRegistry = (typeof widgetRegistry === "object" && widgetRegistry !== null) ? Object.assign({}, widgetRegistry) : {}; + const screenMap = (typeof nextRegistry[widgetId] === "object" && nextRegistry[widgetId] !== null) ? Object.assign({}, nextRegistry[widgetId]) : {}; + screenMap[screenName] = widgetRef; + nextRegistry[widgetId] = screenMap; + widgetRegistry = nextRegistry; widgetRegistered(widgetId, screenName); } @@ -36,15 +35,21 @@ Singleton { if (!widgetRegistry[widgetId]) return; - delete widgetRegistry[widgetId][screenName]; - if (Object.keys(widgetRegistry[widgetId]).length === 0) - delete widgetRegistry[widgetId]; + const nextRegistry = Object.assign({}, widgetRegistry); + const screenMap = (typeof nextRegistry[widgetId] === "object" && nextRegistry[widgetId] !== null) ? Object.assign({}, nextRegistry[widgetId]) : {}; + delete screenMap[screenName]; + if (Object.keys(screenMap).length === 0) { + delete nextRegistry[widgetId]; + } else { + nextRegistry[widgetId] = screenMap; + } + widgetRegistry = nextRegistry; widgetUnregistered(widgetId, screenName); } function getWidget(widgetId, screenName) { - if (!widgetRegistry[widgetId]) + if (typeof widgetRegistry !== "object" || widgetRegistry === null || !widgetRegistry[widgetId]) return null; if (screenName) return widgetRegistry[widgetId][screenName] || null; @@ -54,7 +59,7 @@ Singleton { } function getWidgetOnFocusedScreen(widgetId) { - if (!widgetRegistry[widgetId]) + if (typeof widgetRegistry !== "object" || widgetRegistry === null || !widgetRegistry[widgetId]) return null; const focusedScreen = getFocusedScreenName(); @@ -78,10 +83,14 @@ Singleton { } function getRegisteredWidgetIds() { + if (typeof widgetRegistry !== "object" || widgetRegistry === null) + return []; return Object.keys(widgetRegistry); } function hasWidget(widgetId) { + if (typeof widgetRegistry !== "object" || widgetRegistry === null) + return false; return widgetRegistry[widgetId] && Object.keys(widgetRegistry[widgetId]).length > 0; }