settings: improved auto-nav to subtabs and highlight focus

This commit is contained in:
Lemmy
2026-02-08 18:40:57 -05:00
parent 48ae963ca4
commit c4a83d7e0b
6 changed files with 67 additions and 30 deletions
+33 -22
View File
@@ -231,11 +231,12 @@ Item {
}
}
// Set sub-tab on the currently loaded tab content
// Set sub-tab on the currently loaded tab content. Returns true if an NTabBar was found.
function setSubTabIndex(subTabIndex) {
if (activeTabContent) {
setSubTabRecursive(activeTabContent, subTabIndex);
return setSubTabRecursive(activeTabContent, subTabIndex);
}
return false;
}
function setSubTabRecursive(item, subTabIndex) {
@@ -243,6 +244,16 @@ Item {
return false;
if (item.objectName === "NTabBar") {
// Prepare the sibling NTabView so the index change doesn't animate
if (item.parent) {
for (let j = 0; j < item.parent.children.length; j++) {
const sibling = item.parent.children[j];
if (sibling.objectName === "NTabView" && sibling.setIndexWithoutAnimation) {
sibling.setIndexWithoutAnimation(subTabIndex);
break;
}
}
}
item.currentIndex = subTabIndex;
return true;
}
@@ -346,21 +357,21 @@ Item {
if (root.activeTabContent && targetKey) {
const widget = root.findAndHighlightWidget(root.activeTabContent, targetKey);
if (widget && root.activeScrollView) {
// Scroll widget into view
const mapped = widget.mapToItem(root.activeScrollView.contentItem, 0, 0);
const scrollBar = root.activeScrollView.ScrollBar.vertical;
if (scrollBar) {
const targetPos = (mapped.y - root.activeScrollView.height / 3) / root.activeScrollView.contentHeight;
scrollBar.position = Math.max(0, Math.min(targetPos, 1.0 - scrollBar.size));
}
// Scroll widget into view using the Flickable directly
const flickable = root.activeScrollView.contentItem;
const mapped = widget.mapToItem(flickable.contentItem, 0, 0);
const targetY = mapped.y - flickable.height / 3;
flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height));
// Position highlight overlay
const overlayPos = widget.mapToItem(tabContentArea, 0, 0);
highlightOverlay.x = overlayPos.x - Style.marginM;
highlightOverlay.y = overlayPos.y - Style.marginM;
highlightOverlay.width = widget.width + Style.marginM * 2;
highlightOverlay.height = widget.height + Style.marginM * 2;
highlightAnimation.restart();
// Position highlight overlay after scroll layout has settled
Qt.callLater(function () {
const overlayPos = widget.mapToItem(tabContentArea, 0, 0);
highlightOverlay.x = overlayPos.x - Style.marginM;
highlightOverlay.y = overlayPos.y - Style.marginM;
highlightOverlay.width = widget.width + Style.marginM * 2;
highlightOverlay.height = widget.height + Style.marginM * 2;
highlightAnimation.restart();
});
}
}
targetKey = "";
@@ -1226,13 +1237,13 @@ Item {
item.screen = root.screen;
}
root.activeTabContent = item;
if (root.highlightLabelKey) {
if (root._pendingSubTab >= 0) {
root.navigatingFromSearch = true;
root.setSubTabIndex(root._pendingSubTab);
root.navigatingFromSearch = false;
if (root._pendingSubTab >= 0) {
root.navigatingFromSearch = true;
if (root.setSubTabIndex(root._pendingSubTab))
root._pendingSubTab = -1;
}
root.navigatingFromSearch = false;
}
if (root.highlightLabelKey) {
highlightScrollTimer.targetKey = root.highlightLabelKey;
highlightScrollTimer.restart();
}
+2 -3
View File
@@ -165,12 +165,11 @@ SmartPanel {
Qt.callLater(() => _settingsContent.navigateToResult(entry));
} else {
_settingsContent.requestedTab = requestedTab;
_settingsContent.initialize();
if (requestedSubTab >= 0) {
const subTab = requestedSubTab;
_settingsContent._pendingSubTab = requestedSubTab;
requestedSubTab = -1;
Qt.callLater(() => _settingsContent.navigateToTab(requestedTab, subTab));
}
_settingsContent.initialize();
}
}
}
@@ -38,13 +38,11 @@ FloatingWindow {
Qt.callLater(() => settingsContent.navigateToResult(entry));
} else {
settingsContent.requestedTab = SettingsPanelService.requestedTab;
settingsContent.initialize();
if (SettingsPanelService.requestedSubTab >= 0) {
const tab = SettingsPanelService.requestedTab;
const subTab = SettingsPanelService.requestedSubTab;
settingsContent._pendingSubTab = SettingsPanelService.requestedSubTab;
SettingsPanelService.requestedSubTab = -1;
Qt.callLater(() => settingsContent.navigateToTab(tab, subTab));
}
settingsContent.initialize();
}
isInitialized = true;
}
@@ -19,7 +19,7 @@ ColumnLayout {
model: Quickshell.screens || []
delegate: NBox {
Layout.fillWidth: true
implicitHeight: contentCol.implicitHeight + Style.marginL * 2
implicitHeight: Math.round(contentCol.implicitHeight + Style.marginL * 2)
color: Color.mSurface
property var brightnessMonitor: BrightnessService.getMonitorForScreen(modelData)
@@ -225,6 +225,11 @@ def resolve_tab_info(
sub_label = subtab_labels[idx] if idx < len(subtab_labels) else None
return tab_index, tab_label, idx, sub_label
except ValueError:
# File doesn't map to any subtab (e.g. a dialog). If the parent tab
# has subtabs, the focus ring can't reach widgets inside dialogs, so
# exclude them from the index.
if subtab_names:
return None, None, None, None
return tab_index, tab_label, None, None
+24
View File
@@ -4,6 +4,7 @@ import qs.Commons
Item {
id: root
objectName: "NTabView"
property int currentIndex: 0
@@ -29,6 +30,29 @@ Item {
anchors.fill: parent
}
// Set the visible tab to idx without triggering a slide animation.
// Call this BEFORE the bound currentIndex changes so that
// onCurrentIndexChanged sees previousIndex === currentIndex and skips.
function setIndexWithoutAnimation(idx) {
fromXAnim.stop();
fromOpacityAnim.stop();
toXAnim.stop();
toOpacityAnim.stop();
animating = false;
previousIndex = idx;
for (let i = 0; i < contentItems.length; i++) {
if (i === idx) {
contentItems[i].x = 0;
contentItems[i].visible = true;
contentItems[i].opacity = 1.0;
} else {
contentItems[i].x = root.width;
contentItems[i].visible = false;
contentItems[i].opacity = 1.0;
}
}
}
Component.onCompleted: {
_initializeItems();
}