diff --git a/.agents/skills/dms-plugin-dev/references/dash-plugin-guide.md b/.agents/skills/dms-plugin-dev/references/dash-plugin-guide.md index 8f3c33e54..014630631 100644 --- a/.agents/skills/dms-plugin-dev/references/dash-plugin-guide.md +++ b/.agents/skills/dms-plugin-dev/references/dash-plugin-guide.md @@ -93,7 +93,7 @@ Injected: `pluginId`, `pluginService`, `popoutService`, `entryId`, `dashHost` (` Base provides: `options`, `pluginData`, `getData(key, def)`, `setData(key, value)`. -Optional: `implicitHeight`, `focusTarget`, `restoreFocus()`, `handleKeyEvent(event)` (return true to consume; Ctrl+Tab and Ctrl+PgUp/PgDown are reserved), `blocksTabNavigation`, `menuActions`, `signal tabRequested(string id)`, `signal navFocusRequested`. +Optional: `implicitHeight`, `focusTarget`, `restoreFocus()`, `handleKeyEvent(event)` (return true to consume; Tab, Ctrl+Tab, F6 and Ctrl+PgUp/PgDown are reserved), `blocksTabNavigation`, `menuActions`, `signal tabRequested(string id)`, `signal navFocusRequested`. For tiles inside a tab, use `Card` from `qs.Modules.DankDash.Overview` so colors, radius and tones match the overview. diff --git a/quickshell/Modules/DDash/DDashPopout.qml b/quickshell/Modules/DDash/DDashPopout.qml index c032a64cf..6d869ef25 100644 --- a/quickshell/Modules/DDash/DDashPopout.qml +++ b/quickshell/Modules/DDash/DDashPopout.qml @@ -383,6 +383,7 @@ DPopout { contentScope: pages.currentHost contentTarget: pages.focusTarget headerTargets: root.editMode ? pageActions.focusTargets : [tabBar, pageTitle.focusTarget] + tabsShown: root.showTabs headerEdge: root.verticalNavigation ? (root.navigationEdge === SettingsData.Right ? "right" : "left") : (root.navigationEdge === SettingsData.Bottom ? "down" : "up") onTabCycleRequested: direction => { root.cycleTab(direction); @@ -390,6 +391,7 @@ DPopout { mainContainer.focusInitial(); } onEditModeRequested: on => root.editMode = on + onLeaveRequested: mainContainer.focusInitial() onOptionsDismissRequested: tabOptions.dismiss() onCloseRequested: { if (root.detailTabId !== "") @@ -468,7 +470,6 @@ DPopout { onEditRequested: root.editMode = true visible: root.showTabs && !root.editMode orientation: root.verticalNavigation ? Qt.Vertical : Qt.Horizontal - tabHighlights: true currentIndex: root.currentTabIndex nextFocusTarget: pageActions.focusTargets[0] ?? pages.focusTarget previousFocusTarget: pages.currentItem?.previousFocusTarget ?? pages.focusTarget diff --git a/quickshell/Modules/DDash/DashKeyboard.qml b/quickshell/Modules/DDash/DashKeyboard.qml index d36225da4..70a06d002 100644 --- a/quickshell/Modules/DDash/DashKeyboard.qml +++ b/quickshell/Modules/DDash/DashKeyboard.qml @@ -16,6 +16,13 @@ Item { property Item contentTarget: null property var headerTargets: [] property string headerEdge: "up" + property bool tabsShown: true + // Outside the content, Tab switches tabs and the tab keeps Space and the arrows. + property bool steppedIn: false + + onActiveChanged: steppedIn = false + onTabItemChanged: steppedIn = false + onEditModeChanged: steppedIn = false readonly property bool idle: active && !optionsShown && !menuOpen readonly property bool contentFocused: contentScope?.activeFocus ?? false @@ -26,31 +33,39 @@ Item { signal editModeRequested(bool on) signal optionsDismissRequested signal closeRequested + signal leaveRequested function visibleHeaderTargets() { return headerTargets.filter(item => item && item.visible && item.enabled); } + // Outside edit mode the header is not a keyboard stop; leaving the content goes back to the tabs. function focusHeader(backwards) { + if (!editMode) { + leaveContent(); + return; + } const targets = visibleHeaderTargets(); const target = backwards ? targets[targets.length - 1] : targets[0]; target?.forceActiveFocus(backwards ? Qt.BacktabFocusReason : Qt.TabFocusReason); } function enterContent(backwards) { + steppedIn = true; const target = backwards ? (tabItem?.previousFocusTarget ?? contentTarget) : contentTarget; FocusNavigation.focusItem(target, backwards); } - function cycleRegion(backwards) { - const targets = visibleHeaderTargets(); - if (!targets.some(FocusNavigation.containsFocus) && targets.length > 0) { - focusHeader(backwards); - return; - } - if (FocusNavigation.moveFocus(targets, backwards)) - return; - enterContent(backwards); + function leaveContent() { + steppedIn = false; + leaveRequested(); + } + + function toggleContent(backwards) { + if (steppedIn) + leaveContent(); + else + enterContent(backwards); } function inContent(item) { @@ -61,6 +76,13 @@ Item { return false; } + function tabKey(backwards) { + if (tabsShown && !steppedIn) + tabCycleRequested(backwards ? -1 : 1); + else + walkContent(backwards); + } + // Tab wraps inside the content, as in Settings. function walkContent(backwards) { if (!contentFocused) { @@ -77,24 +99,14 @@ Item { } } - // A tab without restoreFocus parks focus on the host, which counts as content. - function toggleRegion() { - if (headerFocused || visibleHeaderTargets().length === 0) - enterContent(false); - else - focusHeader(false); - } - function moveCard(direction) { if (!contentFocused) { - if (direction === headerEdge) - focusHeader(false); - else + if (direction !== headerEdge) enterContent(false); return; } if (tabItem?.moveCardFocus?.(direction) !== true && direction === headerEdge) - focusHeader(false); + leaveContent(); } function arrowDirection(event) { @@ -155,31 +167,32 @@ Item { return false; if (editMode) editModeRequested(false); + else if (steppedIn && tabsShown) + leaveContent(); else closeRequested(); return true; } - // The tab row takes Tab itself to move its highlight. Shortcut { sequence: "Tab" - enabled: root.browsing && !root.headerFocused + enabled: root.browsing context: Qt.WindowShortcut - onActivated: root.walkContent(false) + onActivated: root.tabKey(false) } Shortcut { sequence: "Shift+Tab" - enabled: root.browsing && !root.headerFocused + enabled: root.browsing context: Qt.WindowShortcut - onActivated: root.walkContent(true) + onActivated: root.tabKey(true) } Shortcut { sequences: ["Ctrl+Tab", "Ctrl+Shift+Tab"] enabled: root.browsing context: Qt.WindowShortcut - onActivated: root.toggleRegion() + onActivated: root.toggleContent(false) } Shortcut { @@ -207,14 +220,14 @@ Item { sequence: "F6" enabled: root.browsing context: Qt.WindowShortcut - onActivated: root.cycleRegion(false) + onActivated: root.toggleContent(false) } Shortcut { sequence: "Shift+F6" enabled: root.browsing context: Qt.WindowShortcut - onActivated: root.cycleRegion(true) + onActivated: root.toggleContent(true) } Shortcut { diff --git a/quickshell/Modules/DIsland/Activities/DashTabFace.qml b/quickshell/Modules/DIsland/Activities/DashTabFace.qml index 0deb5c0f1..f89cb4afe 100644 --- a/quickshell/Modules/DIsland/Activities/DashTabFace.qml +++ b/quickshell/Modules/DIsland/Activities/DashTabFace.qml @@ -131,8 +131,10 @@ FocusScope { contentScope: tabLoader contentTarget: root.tab?.focusTarget ?? null headerTargets: root.editMode ? pageActions.focusTargets : [tabBarLoader.item, sheetHandle.focusTarget] + tabsShown: root.tabsShown onTabCycleRequested: direction => root.controller.cycleDashTab(direction, tabBarLoader.item?.activeFocus ?? false) onEditModeRequested: on => root.editMode = on + onLeaveRequested: root.focusFace() onOptionsDismissRequested: tabOptions.dismiss() onCloseRequested: root.controller.requestCollapse() } @@ -160,7 +162,6 @@ FocusScope { id: tabBar editable: true - tabHighlights: true evenlySpaced: SettingsData.dashTabsEvenlySpaced currentIndex: root.controller.dashTabIds.indexOf(root.entryId) nextFocusTarget: root.tab?.focusTarget ?? null