implement search bar functionality in settings

This commit is contained in:
2026-06-30 22:20:48 +02:00
parent 61019204d8
commit 2b89c8e4a1
30 changed files with 1215 additions and 1342 deletions
+109
View File
@@ -9,6 +9,9 @@ import qs.Config
ColumnLayout {
id: root
// Enables a smooth scroll animation only for search jumps, so normal
// flicking stays instant.
property bool animateScroll: false
readonly property int cappedWidth: Math.min(800, width)
default property Item contentChild
readonly property alias flickable: flickable
@@ -16,8 +19,106 @@ ColumnLayout {
required property SettingsState sState
required property string title
function applySearchAnchor(): void {
if (!sState.searchAnchor)
return;
scrollRetry.tries = 0;
scrollRetry.lastHeight = -1;
scrollRetry.stableFrames = 0;
scrollRetry.restart();
}
function findAnchor(item: Item, anchor: string): Item {
if (!item)
return null;
if (item.settingAnchor !== undefined && item.settingAnchor === anchor) // qmllint disable missing-property
return item;
const kids = item.children;
for (let i = 0; i < kids.length; i++) {
const found = findAnchor(kids[i], anchor);
if (found)
return found;
}
return null;
}
// Flash a row without scrolling (used when re-selecting the current setting).
function highlightAnchor(anchor: string): void {
const row = findAnchor(contentChild, anchor);
if (row && row.flashHighlight !== undefined) // qmllint disable missing-property
row.flashHighlight(); // qmllint disable missing-property
}
// When the settings search jumps to this page, scroll to the matching row.
function scrollToAnchor(anchor: string): bool {
if (!anchor || !contentChild)
return false;
const row = findAnchor(contentChild, anchor);
if (!row)
return false;
const pos = row.mapToItem(flickable.contentItem, 0, 0);
// Land the row below the top fade so it isn't dimmed by the edge effect,
// clamped to the flickable's real scroll range (which includes margins).
const inset = flickable.height * flickable.fadeAmount + Appearance.padding.large;
const minY = -flickable.topMargin;
const maxY = Math.max(minY, flickable.contentHeight + flickable.bottomMargin - flickable.height);
const target = Math.max(minY, Math.min(pos.y - inset, maxY));
root.animateScroll = true;
flickable.contentY = target;
Qt.callLater(() => root.animateScroll = false);
if (row.flashHighlight !== undefined) // qmllint disable missing-property
row.flashHighlight(); // qmllint disable missing-property
return true;
}
spacing: Appearance.spacing.large
Component.onCompleted: applySearchAnchor()
Timer {
id: scrollRetry
property real lastHeight: -1
property int stableFrames: 0
property int tries: 0
interval: 16
repeat: true
onTriggered: {
// Pages like the ethernet detail load their content asynchronously
// (device info, IP config), so the layout keeps growing for a while.
// Wait until contentHeight has held steady for a few frames (or we've
// waited long enough) before scrolling, so the target doesn't drift.
const h = flickable.contentHeight;
if (h === lastHeight && h > flickable.height)
stableFrames++;
else
stableFrames = 0;
lastHeight = h;
const ready = stableFrames >= 3 || tries >= 30;
if (ready) {
if (root.scrollToAnchor(root.sState.searchAnchor))
root.sState.searchAnchor = "";
stop();
}
tries++;
}
}
Connections {
function onHighlightSetting(anchor: string): void {
root.highlightAnchor(anchor);
}
function onSearchAnchorChanged(): void {
root.applySearchAnchor();
}
target: root.sState
}
MouseArea { // Prevent clicks from reaching flickable
Layout.bottomMargin: -flickable.topMargin // Extra height to block clicks on flickable top margin
@@ -69,6 +170,14 @@ ColumnLayout {
fadeAmount: 0.1
topMargin: Appearance.padding.large
Behavior on contentY {
enabled: root.animateScroll
Anim {
type: Anim.DefaultSpatial
}
}
TapHandler {
onTapped: flickable.focus = true
}