From 7535b70fa6c8f29cecdbe686fa66abc68ce0848d Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 13 Jul 2026 16:11:15 -0400 Subject: [PATCH] dash: allow hiding all tabs port 1.5 --- quickshell/Common/SettingsData.qml | 6 +- quickshell/DMSShellIPC.qml | 22 +++-- quickshell/Modules/DankBar/DankBarContent.qml | 15 ++-- .../DankBar/DankBarHoverController.qml | 6 +- quickshell/Modules/DankBar/DankBarWindow.qml | 8 +- .../Modules/DankDash/DankDashPopout.qml | 90 ++++++++----------- quickshell/Modules/Settings/DankDashTab.qml | 10 +-- quickshell/Services/PopoutService.qml | 27 ++++-- quickshell/Widgets/DankTabBar.qml | 2 + 9 files changed, 85 insertions(+), 101 deletions(-) diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index d497154ec..db631db48 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -619,11 +619,7 @@ Singleton { } function setDashTabEnabled(id, on) { - const current = getDashTabs(); - const visibleContentIds = visibleDashTabIds().filter(tabId => tabId !== "settings"); - if (!on && visibleContentIds.indexOf(id) >= 0 && visibleContentIds.length <= 1) - return; - dashTabs = current.map(t => t.id === id ? { + dashTabs = getDashTabs().map(t => t.id === id ? { "id": t.id, "enabled": on } : t); diff --git a/quickshell/DMSShellIPC.qml b/quickshell/DMSShellIPC.qml index 8359819a2..35dcb1c7c 100644 --- a/quickshell/DMSShellIPC.qml +++ b/quickshell/DMSShellIPC.qml @@ -272,34 +272,38 @@ Item { } IpcHandler { - function resolveTabIndex(tab: string): int { + function _resolveTabId(tab) { switch ((tab || "").toLowerCase()) { case "media": - return SettingsData.dashTabIndexForId("media"); + return "media"; case "wallpaper": - return SettingsData.dashTabIndexForId("wallpaper"); + return "wallpaper"; case "weather": - return SettingsData.dashTabIndexForId("weather"); + return "weather"; default: - return SettingsData.dashTabIndexForId("overview"); + return "overview"; } } + function resolveTabIndex(tab: string): int { + return SettingsData.dashTabIndexForId(_resolveTabId(tab)); + } + function open(tab: string): string { const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar(); if (!bar) return "DASH_OPEN_FAILED"; - const tabIndex = resolveTabIndex(tab); + const tabId = _resolveTabId(tab); const dash = root.dankDashPopoutLoader.item; if (dash && dash.shouldBeVisible && dash.triggerScreen?.name === bar.screen?.name) { - dash.currentTabIndex = tabIndex; + dash.requestTab(tabId); if (dash.updateSurfacePosition) dash.updateSurfacePosition(); return "DASH_OPEN_SUCCESS"; } - if (!bar.triggerDashTab(tabIndex)) + if (!bar.triggerDashTab(tabId)) return "DASH_OPEN_FAILED"; return "DASH_OPEN_SUCCESS"; @@ -321,7 +325,7 @@ Item { const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar(); if (bar) { - if (!bar.triggerDashTab(resolveTabIndex(tab))) + if (!bar.triggerDashTab(_resolveTabId(tab))) return "DASH_TOGGLE_FAILED"; return "DASH_TOGGLE_SUCCESS"; } diff --git a/quickshell/Modules/DankBar/DankBarContent.qml b/quickshell/Modules/DankBar/DankBarContent.qml index 80ff7fe97..37b06115b 100644 --- a/quickshell/Modules/DankBar/DankBarContent.qml +++ b/quickshell/Modules/DankBar/DankBarContent.qml @@ -1105,13 +1105,12 @@ Item { onClockClicked: { const section = topBarContent.getWidgetSection(parent) || "center"; - const tabIndex = SettingsData.dashTabIndexForId("overview"); topBarContent.openWidgetPopout({ loader: dankDashPopoutLoader, widgetItem: clockWidget, section, - tabIndex, - triggerSource: topBarContent._dashTriggerSource(section, tabIndex), + triggerSource: topBarContent._dashTriggerSource(section, "overview"), + prepare: popout => popout.requestTab("overview"), mode: "click", useCenterSection: true, setTriggerScreen: true @@ -1134,13 +1133,12 @@ Item { parentScreen: barWindow.screen onClicked: { const section = topBarContent.getWidgetSection(parent) || "center"; - const tabIndex = SettingsData.dashTabIndexForId("media"); topBarContent.openWidgetPopout({ loader: dankDashPopoutLoader, widgetItem: mediaWidget, section, - tabIndex, - triggerSource: topBarContent._dashTriggerSource(section, tabIndex), + triggerSource: topBarContent._dashTriggerSource(section, "media"), + prepare: popout => popout.requestTab("media"), mode: "click", useCenterSection: true, setTriggerScreen: true @@ -1162,13 +1160,12 @@ Item { parentScreen: barWindow.screen onClicked: { const section = topBarContent.getWidgetSection(parent) || "center"; - const tabIndex = SettingsData.dashTabIndexForId("weather"); topBarContent.openWidgetPopout({ loader: dankDashPopoutLoader, widgetItem: weatherWidget, section, - tabIndex, - triggerSource: topBarContent._dashTriggerSource(section, tabIndex), + triggerSource: topBarContent._dashTriggerSource(section, "weather"), + prepare: popout => popout.requestTab("weather"), mode: "click", useCenterSection: true, setTriggerScreen: true diff --git a/quickshell/Modules/DankBar/DankBarHoverController.qml b/quickshell/Modules/DankBar/DankBarHoverController.qml index 463fdde21..c213ca0ae 100644 --- a/quickshell/Modules/DankBar/DankBarHoverController.qml +++ b/quickshell/Modules/DankBar/DankBarHoverController.qml @@ -736,11 +736,11 @@ Item { case "music": case "weather": { - const tabIndex = SettingsData.dashTabIndexForId(widgetId === "clock" ? "overview" : (widgetId === "music" ? "media" : "weather")); + const tabId = widgetId === "clock" ? "overview" : (widgetId === "music" ? "media" : "weather"); return barContent.openWidgetPopout(Object.assign({}, base, { loader, - tabIndex, - triggerSource: dashTriggerSource(section, tabIndex), + triggerSource: dashTriggerSource(section, tabId), + prepare: popout => popout.requestTab(tabId), useCenterSection: true, setTriggerScreen: true })); diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 22f3a7d8a..ce104199c 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -65,7 +65,7 @@ PanelWindow { } } - function triggerDashTab(tabIndex) { + function triggerDashTab(tabId) { dankDashPopoutLoader.active = true; if (!dankDashPopoutLoader.item) { return false; @@ -103,12 +103,14 @@ PanelWindow { dankDashPopoutLoader.item.triggerScreen = barWindow.screen; } - PopoutManager.requestPopout(dankDashPopoutLoader.item, tabIndex, (barConfig?.id ?? "default") + "-" + section + "-" + tabIndex); + if (dankDashPopoutLoader.item.requestTab) + dankDashPopoutLoader.item.requestTab(tabId); + PopoutManager.requestPopout(dankDashPopoutLoader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId); return true; } function triggerWallpaperBrowser() { - triggerDashTab(SettingsData.dashTabIndexForId("wallpaper")); + triggerDashTab("wallpaper"); } readonly property bool usesOverlayLayer: CompositorService.framePeerSurfacesUseOverlayForScreen(barWindow.screen) || (barConfig?.useOverlayLayer ?? false) diff --git a/quickshell/Modules/DankDash/DankDashPopout.qml b/quickshell/Modules/DankDash/DankDashPopout.qml index 7aea8ada9..10eb1796d 100644 --- a/quickshell/Modules/DankDash/DankDashPopout.qml +++ b/quickshell/Modules/DankDash/DankDashPopout.qml @@ -10,7 +10,7 @@ DankPopout { property bool dashVisible: false property var triggerScreen: null - property int currentTabIndex: 0 + property string currentTabId: "overview" readonly property var __tabPresentation: ({ "overview": { @@ -36,36 +36,34 @@ DankPopout { } }) readonly property var orderedTabIds: SettingsData.visibleDashTabIds() - readonly property string currentTabId: orderedTabIds.length > 0 ? (orderedTabIds[Math.min(currentTabIndex, orderedTabIds.length - 1)] ?? "overview") : "overview" + // -1 when the current view's tab is hidden: the view still shows, no tab is highlighted. + readonly property int currentTabIndex: orderedTabIds.indexOf(currentTabId) function __isActionTab(id) { return root.__tabPresentation[id]?.isAction === true; } - function __resolveContentIndex(idx) { - if (orderedTabIds.length === 0) - return 0; - var clamped = Math.max(0, Math.min(idx, orderedTabIds.length - 1)); - if (!__isActionTab(orderedTabIds[clamped])) - return clamped; - for (var f = clamped + 1; f < orderedTabIds.length; f++) - if (!__isActionTab(orderedTabIds[f])) - return f; - for (var b = clamped - 1; b >= 0; b--) - if (!__isActionTab(orderedTabIds[b])) - return b; - return clamped; + // Show a view regardless of tab visibility; bar widgets and IPC land here. + function requestTab(id) { + const valid = __tabPresentation[id] !== undefined && !__isActionTab(id) && (id !== "weather" || SettingsData.weatherEnabled); + currentTabId = valid ? id : "overview"; } - onOrderedTabIdsChanged: { - var resolved = __resolveContentIndex(currentTabIndex); - if (resolved !== currentTabIndex) - currentTabIndex = resolved; + function __cycleTab(dir) { + const ids = orderedTabIds.filter(id => !__isActionTab(id)); + if (ids.length === 0) + return; + const pos = ids.indexOf(currentTabId); + const next = pos < 0 ? (dir > 0 ? 0 : ids.length - 1) : (pos + dir + ids.length) % ids.length; + currentTabId = ids[next]; } - onCurrentTabIndexChanged: { - var resolved = __resolveContentIndex(currentTabIndex); - if (resolved !== currentTabIndex) - currentTabIndex = resolved; + + Connections { + target: SettingsData + function onWeatherEnabledChanged() { + if (!SettingsData.weatherEnabled && root.currentTabId === "weather") + root.currentTabId = "overview"; + } } popupWidth: SettingsData.showWeekNumber ? 736 : 700 @@ -266,32 +264,13 @@ DankPopout { } if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) { - let nextIndex = root.currentTabIndex + 1; - while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) { - nextIndex++; - } - if (nextIndex >= tabBar.model.length) { - nextIndex = 0; - } - root.currentTabIndex = nextIndex; + root.__cycleTab(1); event.accepted = true; return; } if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) { - let prevIndex = root.currentTabIndex - 1; - while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { - prevIndex--; - } - if (prevIndex < 0) { - prevIndex = tabBar.model.length - 1; - while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) { - prevIndex--; - } - } - if (prevIndex >= 0) { - root.currentTabIndex = prevIndex; - } + root.__cycleTab(-1); event.accepted = true; return; } @@ -331,11 +310,12 @@ DankPopout { // Effective visibility is false while the popout window is unmapped, so gating // height on `visible` collapses the bar between opens and resizes the surface - // mid-animation. Gate on the model instead. - readonly property bool hasTabs: (model?.length ?? 0) > 0 + // mid-animation. Gate on data only. The bar also hides entirely when the + // current view's tab isn't in the visible set (e.g. IPC-opened hidden tab). + readonly property bool showTabs: (model?.length ?? 0) > 0 && root.currentTabIndex >= 0 width: parent.width - height: hasTabs ? 48 : 0 - visible: hasTabs + height: showTabs ? 48 : 0 + visible: showTabs currentIndex: root.currentTabIndex spacing: Theme.spacingS equalWidthTabs: true @@ -354,7 +334,9 @@ DankPopout { model: root.orderedTabIds.map(id => root.__tabPresentation[id]) onTabClicked: function (index) { - root.currentTabIndex = index; + const id = root.orderedTabIds[index]; + if (id !== undefined) + root.currentTabId = id; } onActionTriggered: function (index) { @@ -367,8 +349,8 @@ DankPopout { Item { width: parent.width - height: tabBar.hasTabs ? Theme.spacingXS : 0 - visible: tabBar.hasTabs + height: tabBar.showTabs ? Theme.spacingXS : 0 + visible: tabBar.showTabs } Item { @@ -411,11 +393,11 @@ DankPopout { onNavFocusRequested: mainContainer.forceActiveFocus() onSwitchToWeatherTab: { if (SettingsData.weatherEnabled) { - root.currentTabIndex = SettingsData.dashTabIndexForId("weather"); + root.requestTab("weather"); } } onSwitchToMediaTab: { - root.currentTabIndex = SettingsData.dashTabIndexForId("media"); + root.requestTab("media"); } } } @@ -434,7 +416,7 @@ DankPopout { popoutY: root.alignedY popoutWidth: root.alignedWidth popoutHeight: root.alignedHeight - contentOffsetY: Theme.spacingM + 48 + Theme.spacingS + Theme.spacingXS + contentOffsetY: Theme.spacingM + (tabBar.showTabs ? 48 + Theme.spacingS + Theme.spacingXS : 0) section: root.triggerSection barPosition: root.effectiveBarPosition Component.onCompleted: root.__mediaTabRef = this diff --git a/quickshell/Modules/Settings/DankDashTab.qml b/quickshell/Modules/Settings/DankDashTab.qml index c3b3ced89..343ce7ffb 100644 --- a/quickshell/Modules/Settings/DankDashTab.qml +++ b/quickshell/Modules/Settings/DankDashTab.qml @@ -48,7 +48,6 @@ Item { // its delegates alive across commits (preserving focus for keyboard reorder) readonly property var tabIds: SettingsData._dashTabIds readonly property var tabState: SettingsData.getDashTabs() - readonly property var visibleContentIds: SettingsData.visibleDashTabIds().filter(id => id !== "settings") function presentationFor(id) { return __presentation[id] ?? { @@ -148,10 +147,6 @@ Item { commit(); } - function canHide(id) { - return !isEnabled(id) || id === "settings" || visibleContentIds.indexOf(id) < 0 || visibleContentIds.length > 1; - } - // Keyboard nav is handled at the tab root (not per-row activeFocusOnTab) Keys.onPressed: function (event) { const order = enabledOrder.concat(disabledOrder); @@ -172,8 +167,7 @@ Item { } event.accepted = true; } else if ((event.key === Qt.Key_Space || event.key === Qt.Key_Return) && highlightedId !== "") { - if (canHide(highlightedId)) - SettingsData.setDashTabEnabled(highlightedId, !isEnabled(highlightedId)); + SettingsData.setDashTabEnabled(highlightedId, !isEnabled(highlightedId)); event.accepted = true; } } @@ -379,7 +373,6 @@ Item { readonly property bool isEnabled: root.isEnabled(modelData) readonly property bool dragging: root.draggingId === modelData readonly property bool highlighted: root.highlightedId === modelData - readonly property bool canHide: root.canHide(modelData) width: reorderArea.width height: root.rowHeight @@ -528,7 +521,6 @@ Item { iconName: rowItem.isEnabled ? "visibility" : "visibility_off" iconSize: 18 iconColor: rowItem.isEnabled ? Theme.primary : Theme.outline - enabled: rowItem.canHide onClicked: { root.forceActiveFocus(); root.highlightedId = rowItem.modelData; diff --git a/quickshell/Services/PopoutService.qml b/quickshell/Services/PopoutService.qml index 3d1c2ecd0..00e1292a4 100644 --- a/quickshell/Services/PopoutService.qml +++ b/quickshell/Services/PopoutService.qml @@ -214,7 +214,7 @@ Singleton { property bool _dankDashWantsOpen: false property bool _dankDashWantsToggle: false - property int _dankDashPendingTab: 0 + property var _dankDashPendingTab: 0 property real _dankDashPendingX: 0 property real _dankDashPendingY: 0 property real _dankDashPendingWidth: 0 @@ -231,12 +231,21 @@ Singleton { _dankDashHasPosition = hasPos; } - function openDankDash(tabIndex, x, y, width, section, screen) { - _dankDashPendingTab = tabIndex || 0; + // `tab` is a view id ("weather"); a numeric index into the visible tabs is + // still accepted for plugin compatibility. + function _dankDashTabId(tab) { + if (typeof tab === "string" && tab !== "") + return tab; + const ids = SettingsData.visibleDashTabIds(); + return ids[typeof tab === "number" ? tab : 0] ?? "overview"; + } + + function openDankDash(tab, x, y, width, section, screen) { + _dankDashPendingTab = tab || 0; if (dankDashPopout) { if (arguments.length >= 6) setPosition(dankDashPopout, x, y, width, section, screen); - dankDashPopout.currentTabIndex = _dankDashPendingTab; + dankDashPopout.requestTab(_dankDashTabId(_dankDashPendingTab)); dankDashPopout.dashVisible = true; return; } @@ -259,15 +268,15 @@ Singleton { // bindings while Qt is still unwinding the signal stack. } - function toggleDankDash(tabIndex, x, y, width, section, screen) { - _dankDashPendingTab = tabIndex || 0; + function toggleDankDash(tab, x, y, width, section, screen) { + _dankDashPendingTab = tab || 0; if (dankDashPopout) { if (arguments.length >= 6) setPosition(dankDashPopout, x, y, width, section, screen); if (dankDashPopout.dashVisible) { dankDashPopout.dashVisible = false; } else { - dankDashPopout.currentTabIndex = _dankDashPendingTab; + dankDashPopout.requestTab(_dankDashTabId(_dankDashPendingTab)); dankDashPopout.dashVisible = true; } return; @@ -289,7 +298,7 @@ Singleton { if (_dankDashWantsOpen) { _dankDashWantsOpen = false; - dankDashPopout.currentTabIndex = _dankDashPendingTab; + dankDashPopout.requestTab(_dankDashTabId(_dankDashPendingTab)); dankDashPopout.dashVisible = true; return; } @@ -298,7 +307,7 @@ Singleton { if (dankDashPopout.dashVisible) { dankDashPopout.dashVisible = false; } else { - dankDashPopout.currentTabIndex = _dankDashPendingTab; + dankDashPopout.requestTab(_dankDashTabId(_dankDashPendingTab)); dankDashPopout.dashVisible = true; } } diff --git a/quickshell/Widgets/DankTabBar.qml b/quickshell/Widgets/DankTabBar.qml index eadef0885..27b1d7247 100644 --- a/quickshell/Widgets/DankTabBar.qml +++ b/quickshell/Widgets/DankTabBar.qml @@ -225,6 +225,8 @@ FocusScope { function updateIndicator() { if (tabRepeater.count === 0 || currentIndex < 0 || currentIndex >= tabRepeater.count) { + indicator.visible = false; + indicator.initialSetupComplete = false; return; }