From 3595279dce619b5b3f856ded14e7889a76b23fdd Mon Sep 17 00:00:00 2001 From: Ryan Hughes Date: Thu, 14 May 2026 01:55:58 -0400 Subject: [PATCH] Park focus on a sink Item when leaving body The previous clearBodyFocus() set focus = false on navRoot.activeFocusItem and called navRoot.forceActiveFocus(), which works for plain Rectangles but not for Controls 2 widgets like ComboBox: the combo keeps an internal focused child that the surrounding FocusScope happily restores, so the accent focus border on the Position/Center anchor dropdowns lingered after backing out to the sidebar. Add an invisible 1x1 navFocusSink Item inside navRoot and force focus onto it whenever the user leaves the body (h/Esc/Shift-Tab, category changes from sidebar nav, or panel reopens). The sink reliably owns activeFocus so every body control \u2014 ComboBox included \u2014 falls back to its unfocused style. --- .../plugins/settings/SettingsPanel.qml | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml index e754a7bd..90b6c5fc 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml @@ -30,7 +30,7 @@ Item { root.syncSidebarIndexFromCategory() root.focusZone = "sidebar" window.visible = true - Qt.callLater(function() { if (navRoot) navRoot.forceActiveFocus() }) + Qt.callLater(parkFocusOnSink) } function close() { @@ -108,17 +108,17 @@ Item { function exitBodyZone() { focusZone = "sidebar" - clearBodyFocus() - if (navRoot) navRoot.forceActiveFocus() + parkFocusOnSink() } - // Drop activeFocus from any body item so its focus ring goes away when - // the user backs out to the sidebar. Without this, FocusScope remembers - // the last focused descendant and the highlight lingers. - function clearBodyFocus() { - if (!navRoot) return - var afi = navRoot.activeFocusItem - if (afi && afi !== navRoot) afi.focus = false + // Move activeFocus to a dedicated sink Item that lives outside the body + // tree. Just clearing focus on the previously focused descendant isn't + // enough — controls like ComboBox keep an internal focused child that + // FocusScope happily restores. Forcing focus onto a known sink reliably + // clears every body focus ring. + function parkFocusOnSink() { + if (typeof navFocusSink !== "undefined" && navFocusSink) navFocusSink.forceActiveFocus() + else if (navRoot) navRoot.forceActiveFocus() } // Walk the visible body subtree and collect any item with @@ -181,7 +181,7 @@ Item { onActiveCategoryChanged: { syncSidebarIndexFromCategory() if (focusZone === "body") Qt.callLater(focusFirstBodyItem) - else clearBodyFocus() + else parkFocusOnSink() } // ---------------- bundled defaults --------------------------------------- @@ -599,7 +599,7 @@ Item { onVisibleChanged: { if (!visible && !root.closingFromHost && root.shell && typeof root.shell.hide === "function") root.shell.hide("omarchy.settings") - if (visible) Qt.callLater(function() { if (navRoot) navRoot.forceActiveFocus() }) + if (visible) Qt.callLater(root.parkFocusOnSink) } FocusScope { @@ -607,7 +607,17 @@ Item { anchors.fill: parent focus: true - Component.onCompleted: forceActiveFocus() + Component.onCompleted: navFocusSink.forceActiveFocus() + + // Invisible focus sink. When focus belongs to the sidebar (no + // specific body item focused), activeFocus lives on this 1px Item so + // body controls render their unfocused state cleanly. + Item { + id: navFocusSink + width: 1 + height: 1 + objectName: "navFocusSink" + } Keys.priority: Keys.BeforeItem Keys.onPressed: function(event) {