aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/Settings.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-17 10:06:12 +0200
committerDanilo M. <danix@danix.xyz>2026-10-17 10:06:12 +0200
commit1c68c391cd10b5726d082d487b97e7decb823512 (patch)
tree803bf7f950f3475d122742f2588d6a8daa189a13 /desktop/Settings.qml
parent74fde3e0dae5a40c522ada14cf04945f3c806730 (diff)
downloadquickshell-1c68c391cd10b5726d082d487b97e7decb823512.tar.gz
quickshell-1c68c391cd10b5726d082d487b97e7decb823512.zip
desktop: settings search finds rows, and a hit reveals its row
Search used to stop at the page: clicking a result opened it at the top and left the setting to be found by eye. Now each settings module keeps its rows as searchItems, {text, key}, reread on every window open since the page itself is not loaded until shown: git runs gitcfg list, and autostart watches its file. A row hit opens the page, and Page.reveal() finds the row whose objectName is the key, waiting up to 3s for the page's own data, scrolls to it and flashes it in the accent. The reveal lives in Page, so a page only names its rows. Git's long keyword list was a stand-in for exactly this and is cut to "gitconfig". The autostart page takes its path from the module now that both read the file. Also corrects the AGENTS note on missed hot reloads: it blamed sed -i replacing the file, but the Edit tool writes a new inode too and usually reloads. The cause is unknown; the note now says how to detect it and that a process that stops watching needs a restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/Settings.qml')
-rw-r--r--desktop/Settings.qml43
1 files changed, 33 insertions, 10 deletions
diff --git a/desktop/Settings.qml b/desktop/Settings.qml
index 52ca549..9882193 100644
--- a/desktop/Settings.qml
+++ b/desktop/Settings.qml
@@ -32,22 +32,40 @@ Scope {
}
property string query: ""
- // The pages the search keeps, as {mod, hit}: hit is the keyword that
- // matched when the label did not, shown under the entry.
+ // What the search keeps, as {mod, hit, key}. A page matched by its label
+ // or a keyword has key "" and opens at the top; hit is the keyword when
+ // the label did not match. Then each row inside a page that matches, from
+ // the module's searchItems, with key naming that row.
+ readonly property int perPage: 8
readonly property var found: {
const q = root.query.trim().toLowerCase();
const out = [];
for (const m of root.pages) {
- if (q === "" || m.label.toLowerCase().includes(q) || m.name.includes(q)) {
- out.push({ mod: m, hit: "" });
- continue;
+ if (q === "" || m.label.toLowerCase().includes(q) || m.name.includes(q))
+ out.push({ mod: m, hit: "", key: "" });
+ else {
+ const k = Array.from(m.keywords).find(w => w.toLowerCase().includes(q));
+ if (k !== undefined) out.push({ mod: m, hit: k, key: "" });
}
- const k = Array.from(m.keywords).find(w => w.toLowerCase().includes(q));
- if (k !== undefined) out.push({ mod: m, hit: k });
+ if (q === "") continue;
+ // ponytail: the first perPage rows per page; a narrower query finds the rest.
+ for (const it of m.searchItems.filter(it => it.text.toLowerCase().includes(q)).slice(0, root.perPage))
+ out.push({ mod: m, hit: it.text, key: it.key });
}
return out;
}
+ // The page is shown first so the Loader holds it, then asked to reveal.
+ signal reveal(string key)
+ function go(entry) {
+ root.page = entry.mod.name;
+ if (entry.key) root.reveal(entry.key);
+ }
+
+ // The search index is the modules' own copy of their rows, reread on
+ // every open so it matches what the pages will show.
+ onOpenChanged: if (root.open) for (const m of root.pages) m.refresh()
+
// Null is the landing page.
readonly property QtObject current: root.pages.find(m => m.name === root.page) ?? null
@@ -96,7 +114,7 @@ Scope {
font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
text: root.query
onTextEdited: root.query = text
- onAccepted: if (root.found.length) root.page = root.found[0].mod.name
+ onAccepted: if (root.found.length) root.go(root.found[0])
Keys.onEscapePressed: event => {
if (root.query !== "") root.query = "";
else event.accepted = false;
@@ -121,7 +139,7 @@ Scope {
delegate: Rectangle {
id: entry
required property var modelData
- readonly property bool selected: root.current === modelData.mod
+ readonly property bool selected: root.current === modelData.mod && modelData.key === ""
width: side.width
height: modelData.hit ? 46 : 36
radius: 8
@@ -159,7 +177,7 @@ Scope {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
- onClicked: root.page = entry.modelData.mod.name
+ onClicked: root.go(entry.modelData)
}
}
}
@@ -225,6 +243,11 @@ Scope {
// The Page's back arrow steps out to the landing page,
// dismiss closes the window, and go() moves to another
// settings page by name.
+ Connections {
+ target: root
+ function onReveal(key) { if (pageLoader.item?.reveal) pageLoader.item.reveal(key); }
+ }
+
onLoaded: {
if (item && item.back) item.back.connect(root.back);
if (item && item.dismiss) item.dismiss.connect(root.close);