From b03a7cc73c56d024d404b90d4e7746ba59e9cd12 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 27 May 2026 10:40:32 +0200 Subject: [PATCH] Use Tab to switch bar panels --- shell/Ui/Panel.qml | 4 ++ shell/plugins/bar/Bar.qml | 51 ++++++++++++++++++++++++ shell/plugins/panels/audio/Panel.qml | 2 +- shell/plugins/panels/bluetooth/Panel.qml | 1 + shell/plugins/panels/monitor/Panel.qml | 1 + shell/plugins/panels/network/Panel.qml | 1 + shell/plugins/panels/power/Panel.qml | 1 + shell/plugins/panels/tailscale/Panel.qml | 1 + 8 files changed, 61 insertions(+), 1 deletion(-) diff --git a/shell/Ui/Panel.qml b/shell/Ui/Panel.qml index 957ebe02..2fa3c70f 100644 --- a/shell/Ui/Panel.qml +++ b/shell/Ui/Panel.qml @@ -27,6 +27,10 @@ Item { Qt.callLater(function() { popoutSwitchClosing = false }) } function toggle() { opened ? close() : open() } + function switchPanel(direction) { + if (bar && typeof bar.switchPanelFrom === "function") return bar.switchPanelFrom(root, direction) + return false + } // Read a single value from this panel's inline shell.json entry, with a // fallback for missing/null values. Matches BarWidget.setting(). diff --git a/shell/plugins/bar/Bar.qml b/shell/plugins/bar/Bar.qml index bef7d58c..3d46a91b 100644 --- a/shell/plugins/bar/Bar.qml +++ b/shell/plugins/bar/Bar.qml @@ -191,6 +191,57 @@ Item { return Array.isArray(entries) ? entries : [] } + function panelNavigationSlots(region) { + var entries = layoutEntries(region) + var slots = [] + for (var i = 0; i < entries.length; i++) { + var id = entryId(entries[i]) + for (var j = 0; j < debugModuleSlots.length; j++) { + var slot = debugModuleSlots[j] + if (!slot || slot.region !== region || slot.moduleName !== id) continue + var item = slot.activeItem + if (!item || item.visible !== true || slot.visible !== true || slot.width <= 0 || slot.height <= 0) continue + if (typeof item.open !== "function" || typeof item.close !== "function" || item.opened === undefined) continue + slots.push(slot) + break + } + } + return slots + } + + function switchPanelFrom(owner, direction) { + if (!owner) return false + + var currentSlot = null + for (var i = 0; i < debugModuleSlots.length; i++) { + var slot = debugModuleSlots[i] + if (slot && slot.activeItem === owner) { + currentSlot = slot + break + } + } + if (!currentSlot) return false + + var slots = panelNavigationSlots(currentSlot.region) + if (slots.length < 2) return false + + var currentIndex = -1 + for (var j = 0; j < slots.length; j++) { + if (slots[j] === currentSlot) { + currentIndex = j + break + } + } + if (currentIndex < 0) return false + + var step = direction < 0 ? -1 : 1 + var nextSlot = slots[(currentIndex + step + slots.length) % slots.length] + if (!nextSlot || !nextSlot.activeItem || nextSlot.activeItem === owner) return false + + nextSlot.activeItem.open() + return true + } + function entrySettings(entry) { return BarModel.entrySettings(entry) } diff --git a/shell/plugins/panels/audio/Panel.qml b/shell/plugins/panels/audio/Panel.qml index e471d348..19a56e09 100644 --- a/shell/plugins/panels/audio/Panel.qml +++ b/shell/plugins/panels/audio/Panel.qml @@ -559,7 +559,7 @@ Panel { } onActivateRequested: if (root.cursorActive) root.activateCursor() onCloseRequested: root.close() - onTabRequested: function(direction) { root.moveSection(direction) } + onTabRequested: function(direction) { root.switchPanel(direction) } onTextKey: function(t) { // 'm' mutes whatever the cursor is on: focused section's slider // for output/input, the focused stream for streams. diff --git a/shell/plugins/panels/bluetooth/Panel.qml b/shell/plugins/panels/bluetooth/Panel.qml index 238f41ea..9c17f4ac 100644 --- a/shell/plugins/panels/bluetooth/Panel.qml +++ b/shell/plugins/panels/bluetooth/Panel.qml @@ -434,6 +434,7 @@ Panel { } onActivateRequested: if (root.cursorActive) root.activateCursor() onCloseRequested: root.close() + onTabRequested: function(direction) { root.switchPanel(direction) } onDeleteRequested: if (root.cursorActive) root.deleteSelected() Column { diff --git a/shell/plugins/panels/monitor/Panel.qml b/shell/plugins/panels/monitor/Panel.qml index e6cc62ba..dc8267f2 100644 --- a/shell/plugins/panels/monitor/Panel.qml +++ b/shell/plugins/panels/monitor/Panel.qml @@ -372,6 +372,7 @@ Panel { } onActivateRequested: if (root.cursorActive) root.activateCursor() onCloseRequested: root.close() + onTabRequested: function(direction) { root.switchPanel(direction) } ScrollView { id: scrollArea diff --git a/shell/plugins/panels/network/Panel.qml b/shell/plugins/panels/network/Panel.qml index bc506593..eb01318e 100644 --- a/shell/plugins/panels/network/Panel.qml +++ b/shell/plugins/panels/network/Panel.qml @@ -664,6 +664,7 @@ Panel { } } onCloseRequested: root.close() + onTabRequested: function(direction) { root.switchPanel(direction) } onTextKey: function(t) { if (t === "r" || t === "R") root.refresh() } diff --git a/shell/plugins/panels/power/Panel.qml b/shell/plugins/panels/power/Panel.qml index f903e360..08f41ab5 100644 --- a/shell/plugins/panels/power/Panel.qml +++ b/shell/plugins/panels/power/Panel.qml @@ -279,6 +279,7 @@ Panel { } onActivateRequested: if (root.cursorActive) root.activateSelectedProfile() onCloseRequested: root.close() + onTabRequested: function(direction) { root.switchPanel(direction) } Column { id: column diff --git a/shell/plugins/panels/tailscale/Panel.qml b/shell/plugins/panels/tailscale/Panel.qml index 11ac0f68..fd36b239 100644 --- a/shell/plugins/panels/tailscale/Panel.qml +++ b/shell/plugins/panels/tailscale/Panel.qml @@ -296,6 +296,7 @@ Panel { } onActivateRequested: if (root.cursorActive) root.activateCursor() onCloseRequested: root.close() + onTabRequested: function(direction) { root.switchPanel(direction) } onTextKey: function(t) { if (t === "t" || t === "T") tailscale.toggleTailscale() else if (t === "c" || t === "C") tailscale.copyPeerIp(root.selectedPeer())