From 3114ecb69024552a66bf1e2c57b0ace281b011bc Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 25 Mar 2026 18:19:37 +0100 Subject: [PATCH] test nix --- Helpers/SettingsHighlight.qml | 27 + Modules/Settings/Categories.qml | 15 +- Modules/Settings/Categories/Appearance.qml | 8 + Modules/Settings/Categories/Background.qml | 2 + Modules/Settings/Categories/Bar.qml | 8 + Modules/Settings/Categories/Dashboard.qml | 6 + Modules/Settings/Categories/General.qml | 5 + Modules/Settings/Categories/Launcher.qml | 8 + Modules/Settings/Categories/Lockscreen.qml | 4 + Modules/Settings/Categories/Notifications.qml | 4 + Modules/Settings/Categories/Osd.qml | 4 + Modules/Settings/Categories/Services.qml | 4 + Modules/Settings/Categories/Sidebar.qml | 2 + Modules/Settings/Categories/Utilities.qml | 6 + Modules/Settings/Content.qml | 46 +- .../Settings/Controls/SettingActionList.qml | 18 + .../Settings/Controls/SettingAliasList.qml | 18 + .../Settings/Controls/SettingBarEntryList.qml | 17 + .../Settings/Controls/SettingHyprSpinner.qml | 16 + Modules/Settings/Controls/SettingInput.qml | 16 + Modules/Settings/Controls/SettingReadOnly.qml | 16 + Modules/Settings/Controls/SettingSpinBox.qml | 20 +- Modules/Settings/Controls/SettingSpinner.qml | 16 + .../Settings/Controls/SettingStringList.qml | 18 +- Modules/Settings/Controls/SettingSwitch.qml | 16 + Modules/Settings/Controls/SettingsPage.qml | 18 + Modules/Settings/Controls/SettingsSection.qml | 1 + Modules/Settings/SettingsIndex.mjs | 1136 +++++++++++++++++ Modules/Settings/SettingsSearch.qml | 310 +++++ nix/default.nix | 14 +- 30 files changed, 1787 insertions(+), 12 deletions(-) create mode 100644 Helpers/SettingsHighlight.qml create mode 100644 Modules/Settings/SettingsIndex.mjs create mode 100644 Modules/Settings/SettingsSearch.qml diff --git a/Helpers/SettingsHighlight.qml b/Helpers/SettingsHighlight.qml new file mode 100644 index 0000000..535eb0f --- /dev/null +++ b/Helpers/SettingsHighlight.qml @@ -0,0 +1,27 @@ +pragma Singleton + +import Quickshell +import QtQuick + +Singleton { + id: root + + property string highlightedSetting: "" + + function clear() { + highlightedSetting = ""; + } + + function highlight(settingName: string) { + highlightedSetting = settingName; + highlightTimer.restart(); + } + + Timer { + id: highlightTimer + + interval: 2000 + + onTriggered: root.clear() + } +} diff --git a/Modules/Settings/Categories.qml b/Modules/Settings/Categories.qml index 731428c..11f8fc8 100644 --- a/Modules/Settings/Categories.qml +++ b/Modules/Settings/Categories.qml @@ -14,7 +14,20 @@ Item { required property Item content - implicitHeight: clayout.contentHeight + Appearance.padding.smaller * 2 + signal settingSelected(string category, string section, string settingName) + + // Function to select category by key + function selectCategory(categoryKey: string) { + for (let i = 0; i < listModel.count; i++) { + if (listModel.get(i).key === categoryKey) { + clayout.currentIndex = i; + root.content.currentCategory = categoryKey; + return; + } + } + } + + implicitHeight: searchBar.implicitHeight + Appearance.spacing.smaller + clayout.contentHeight + Appearance.padding.smaller * 2 implicitWidth: clayout.contentWidth + Appearance.padding.smaller * 2 ListModel { diff --git a/Modules/Settings/Categories/Appearance.qml b/Modules/Settings/Categories/Appearance.qml index 01c88e5..ddeaf3b 100644 --- a/Modules/Settings/Categories/Appearance.qml +++ b/Modules/Settings/Categories/Appearance.qml @@ -5,6 +5,8 @@ SettingsPage { id: root SettingsSection { + sectionId: "Scale" + SettingsHeader { name: "Scale" } @@ -58,6 +60,8 @@ SettingsPage { } SettingsSection { + sectionId: "Fonts" + SettingsHeader { name: "Fonts" } @@ -97,6 +101,8 @@ SettingsPage { } SettingsSection { + sectionId: "Animation" + SettingsHeader { name: "Animation" } @@ -122,6 +128,8 @@ SettingsPage { } SettingsSection { + sectionId: "Transparency" + SettingsHeader { name: "Transparency" } diff --git a/Modules/Settings/Categories/Background.qml b/Modules/Settings/Categories/Background.qml index b7fabb9..20f5dac 100644 --- a/Modules/Settings/Categories/Background.qml +++ b/Modules/Settings/Categories/Background.qml @@ -5,6 +5,8 @@ SettingsPage { id: root SettingsSection { + sectionId: "Wallpaper" + SettingsHeader { name: "Wallpaper" } diff --git a/Modules/Settings/Categories/Bar.qml b/Modules/Settings/Categories/Bar.qml index 150a709..7ebd5f3 100644 --- a/Modules/Settings/Categories/Bar.qml +++ b/Modules/Settings/Categories/Bar.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Bar" + SettingsHeader { name: "Bar" } @@ -45,6 +47,8 @@ SettingsPage { } SettingsSection { + sectionId: "Popouts" + SettingsHeader { name: "Popouts" } @@ -111,6 +115,8 @@ SettingsPage { } SettingsSection { + sectionId: "Entries" + SettingsHeader { name: "Entries" } @@ -123,6 +129,8 @@ SettingsPage { } SettingsSection { + sectionId: "Dock" + SettingsHeader { name: "Dock" } diff --git a/Modules/Settings/Categories/Dashboard.qml b/Modules/Settings/Categories/Dashboard.qml index f597541..dabb37e 100644 --- a/Modules/Settings/Categories/Dashboard.qml +++ b/Modules/Settings/Categories/Dashboard.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Dashboard" + SettingsHeader { name: "Dashboard" } @@ -47,6 +49,8 @@ SettingsPage { } SettingsSection { + sectionId: "Performance" + SettingsHeader { name: "Performance" } @@ -104,6 +108,8 @@ SettingsPage { } SettingsSection { + sectionId: "Layout Sizes" + SettingsHeader { name: "Layout Sizes" } diff --git a/Modules/Settings/Categories/General.qml b/Modules/Settings/Categories/General.qml index 5ec62dc..83d40aa 100644 --- a/Modules/Settings/Categories/General.qml +++ b/Modules/Settings/Categories/General.qml @@ -16,6 +16,8 @@ SettingsPage { } SettingsSection { + sectionId: "General" + SettingsHeader { name: "General" } @@ -46,6 +48,8 @@ SettingsPage { } SettingsSection { + sectionId: "Color" + SettingsHeader { name: "Color" } @@ -175,6 +179,7 @@ SettingsPage { } SettingsSection { + sectionId: "Default Apps" z: -1 SettingsHeader { diff --git a/Modules/Settings/Categories/Launcher.qml b/Modules/Settings/Categories/Launcher.qml index 18cb34f..920bf91 100644 --- a/Modules/Settings/Categories/Launcher.qml +++ b/Modules/Settings/Categories/Launcher.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Launcher" + SettingsHeader { name: "Launcher" } @@ -44,6 +46,8 @@ SettingsPage { } SettingsSection { + sectionId: "Fuzzy Search" + SettingsHeader { name: "Fuzzy Search" } @@ -92,6 +96,8 @@ SettingsPage { } SettingsSection { + sectionId: "Sizes" + SettingsHeader { name: "Sizes" } @@ -135,6 +141,8 @@ SettingsPage { } SettingsSection { + sectionId: "Actions" + SettingsHeader { name: "Actions" } diff --git a/Modules/Settings/Categories/Lockscreen.qml b/Modules/Settings/Categories/Lockscreen.qml index 7c9d2d2..4c6c3e3 100644 --- a/Modules/Settings/Categories/Lockscreen.qml +++ b/Modules/Settings/Categories/Lockscreen.qml @@ -6,6 +6,8 @@ SettingsPage { id: root SettingsSection { + sectionId: "Lockscreen" + SettingsHeader { name: "Lockscreen" } @@ -84,6 +86,8 @@ SettingsPage { } SettingsSection { + sectionId: "Idle" + Idle { } } diff --git a/Modules/Settings/Categories/Notifications.qml b/Modules/Settings/Categories/Notifications.qml index ef0486b..a338724 100644 --- a/Modules/Settings/Categories/Notifications.qml +++ b/Modules/Settings/Categories/Notifications.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Notifications" + SettingsHeader { name: "Notifications" } @@ -78,6 +80,8 @@ SettingsPage { } SettingsSection { + sectionId: "Sizes" + SettingsHeader { name: "Sizes" } diff --git a/Modules/Settings/Categories/Osd.qml b/Modules/Settings/Categories/Osd.qml index 34d5724..e40b147 100644 --- a/Modules/Settings/Categories/Osd.qml +++ b/Modules/Settings/Categories/Osd.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "On Screen Display" + SettingsHeader { name: "On Screen Display" } @@ -53,6 +55,8 @@ SettingsPage { } SettingsSection { + sectionId: "Sizes" + SettingsHeader { name: "Sizes" } diff --git a/Modules/Settings/Categories/Services.qml b/Modules/Settings/Categories/Services.qml index bae8112..3d34fea 100644 --- a/Modules/Settings/Categories/Services.qml +++ b/Modules/Settings/Categories/Services.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Services" + SettingsHeader { name: "Services" } @@ -51,6 +53,8 @@ SettingsPage { } SettingsSection { + sectionId: "Media" + SettingsHeader { name: "Media" } diff --git a/Modules/Settings/Categories/Sidebar.qml b/Modules/Settings/Categories/Sidebar.qml index b661e04..8e07ae6 100644 --- a/Modules/Settings/Categories/Sidebar.qml +++ b/Modules/Settings/Categories/Sidebar.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Sidebar" + SettingsHeader { name: "Sidebar" } diff --git a/Modules/Settings/Categories/Utilities.qml b/Modules/Settings/Categories/Utilities.qml index 3ec1e15..0fb6bcd 100644 --- a/Modules/Settings/Categories/Utilities.qml +++ b/Modules/Settings/Categories/Utilities.qml @@ -3,6 +3,8 @@ import qs.Config SettingsPage { SettingsSection { + sectionId: "Utilities" + SettingsHeader { name: "Utilities" } @@ -45,6 +47,8 @@ SettingsPage { } SettingsSection { + sectionId: "Toasts" + SettingsHeader { name: "Toasts" } @@ -147,6 +151,8 @@ SettingsPage { } SettingsSection { + sectionId: "VPN" + SettingsHeader { name: "VPN" } diff --git a/Modules/Settings/Content.qml b/Modules/Settings/Content.qml index 0e47180..a1bdad1 100644 --- a/Modules/Settings/Content.qml +++ b/Modules/Settings/Content.qml @@ -14,12 +14,35 @@ Item { property string currentCategory: "general" readonly property real nonAnimHeight: Math.floor(screen.height / 1.5) + viewWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + Math.floor(screen.width / 2) + viewWrapper.anchors.margins * 2 + property string pendingSection: "" + property string pendingSetting: "" required property ShellScreen screen required property PersistentProperties visibilities + function scrollToSetting(section: string, settingName: string) { + // Wait for the StackView transition to complete, then scroll + root.pendingSection = section; + root.pendingSetting = settingName; + scrollTimer.restart(); + } + implicitHeight: nonAnimHeight implicitWidth: nonAnimWidth + Timer { + id: scrollTimer + + interval: 50 + + onTriggered: { + if (root.pendingSection && stack.currentItem) { + stack.currentItem.scrollToSectionAndHighlight(root.pendingSection, root.pendingSetting); + root.pendingSection = ""; + root.pendingSetting = ""; + } + } + } + Connections { function onCurrentCategoryChanged() { stack.pop(); @@ -59,12 +82,26 @@ Item { anchors.margins: Appearance.padding.smaller radius: Appearance.rounding.large - Appearance.padding.smaller + SettingsSearch { + id: searchBar + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + + onSettingSelected: (category, section, settingName) => { + root.selectCategory(category); + root.settingSelected(category, section, settingName); + } + } + Item { id: view anchors.bottom: parent.bottom anchors.left: parent.left - anchors.top: parent.top + anchors.top: searchBar.bottom + anchors.topMargin: Appearance.spacing.smaller implicitWidth: layout.implicitWidth Categories { @@ -72,6 +109,10 @@ Item { anchors.fill: parent content: root + + onSettingSelected: (category, section, settingName) => { + root.scrollToSetting(section, settingName); + } } } @@ -82,7 +123,8 @@ Item { anchors.left: view.right anchors.leftMargin: Appearance.spacing.smaller anchors.right: parent.right - anchors.top: parent.top + anchors.top: searchBar.bottom + anchors.topMargin: Appearance.spacing.smaller color: DynamicColors.tPalette.m3surfaceContainer radius: Appearance.rounding.normal diff --git a/Modules/Settings/Controls/SettingActionList.qml b/Modules/Settings/Controls/SettingActionList.qml index 90ccac5..90c155a 100644 --- a/Modules/Settings/Controls/SettingActionList.qml +++ b/Modules/Settings/Controls/SettingActionList.qml @@ -4,10 +4,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers ColumnLayout { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property string setting @@ -45,6 +47,22 @@ ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: root.implicitHeight + Layout.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + z: -1 + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + CustomText { Layout.fillWidth: true font.pointSize: Appearance.font.size.larger diff --git a/Modules/Settings/Controls/SettingAliasList.qml b/Modules/Settings/Controls/SettingAliasList.qml index f7f8959..e140cd3 100644 --- a/Modules/Settings/Controls/SettingAliasList.qml +++ b/Modules/Settings/Controls/SettingAliasList.qml @@ -4,10 +4,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers ColumnLayout { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property string setting @@ -41,6 +43,22 @@ ColumnLayout { Layout.fillWidth: true spacing: Appearance.spacing.smaller + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: root.implicitHeight + Layout.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + z: -1 + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + CustomText { Layout.fillWidth: true font.pointSize: Appearance.font.size.larger diff --git a/Modules/Settings/Controls/SettingBarEntryList.qml b/Modules/Settings/Controls/SettingBarEntryList.qml index f082133..8104d15 100644 --- a/Modules/Settings/Controls/SettingBarEntryList.qml +++ b/Modules/Settings/Controls/SettingBarEntryList.qml @@ -6,6 +6,7 @@ import QtQuick.Layouts import QtQml.Models import qs.Components import qs.Config +import qs.Helpers Item { id: root @@ -19,6 +20,7 @@ Item { property var draggedModelData: null property string draggedUid: "" property bool dropAnimating: false + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object property var pendingCommitEntries: [] @@ -231,6 +233,21 @@ Item { Component.onCompleted: root.rebuildVisualEntries() + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + z: -1 + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + ParallelAnimation { id: settleAnim diff --git a/Modules/Settings/Controls/SettingHyprSpinner.qml b/Modules/Settings/Controls/SettingHyprSpinner.qml index d876e07..456b666 100644 --- a/Modules/Settings/Controls/SettingHyprSpinner.qml +++ b/Modules/Settings/Controls/SettingHyprSpinner.qml @@ -2,10 +2,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property list settings @@ -33,6 +35,20 @@ Item { Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingInput.qml b/Modules/Settings/Controls/SettingInput.qml index 1421664..b5e7c15 100644 --- a/Modules/Settings/Controls/SettingInput.qml +++ b/Modules/Settings/Controls/SettingInput.qml @@ -2,10 +2,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property string setting @@ -22,6 +24,20 @@ Item { Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingReadOnly.qml b/Modules/Settings/Controls/SettingReadOnly.qml index 1fe580a..73969a0 100644 --- a/Modules/Settings/Controls/SettingReadOnly.qml +++ b/Modules/Settings/Controls/SettingReadOnly.qml @@ -2,16 +2,32 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property string value Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingSpinBox.qml b/Modules/Settings/Controls/SettingSpinBox.qml index 0cbf240..b3c1661 100644 --- a/Modules/Settings/Controls/SettingSpinBox.qml +++ b/Modules/Settings/Controls/SettingSpinBox.qml @@ -2,20 +2,36 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name + property real max: Infinity + property real min: -Infinity required property string name required property var object required property string setting - property real max: Infinity - property real min: -Infinity property real step: 1 Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingSpinner.qml b/Modules/Settings/Controls/SettingSpinner.qml index 60c5c26..c3f3790 100644 --- a/Modules/Settings/Controls/SettingSpinner.qml +++ b/Modules/Settings/Controls/SettingSpinner.qml @@ -2,10 +2,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property list settings @@ -33,6 +35,20 @@ Item { Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingStringList.qml b/Modules/Settings/Controls/SettingStringList.qml index c78414b..c3bfcae 100644 --- a/Modules/Settings/Controls/SettingStringList.qml +++ b/Modules/Settings/Controls/SettingStringList.qml @@ -2,18 +2,34 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + property string addLabel: qsTr("Add entry") + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property string setting - property string addLabel: qsTr("Add entry") Layout.fillWidth: true Layout.preferredHeight: layout.implicitHeight + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + ColumnLayout { id: layout diff --git a/Modules/Settings/Controls/SettingSwitch.qml b/Modules/Settings/Controls/SettingSwitch.qml index 09cff93..b87ad4d 100644 --- a/Modules/Settings/Controls/SettingSwitch.qml +++ b/Modules/Settings/Controls/SettingSwitch.qml @@ -2,10 +2,12 @@ import QtQuick import QtQuick.Layouts import qs.Components import qs.Config +import qs.Helpers Item { id: root + readonly property bool highlighted: SettingsHighlight.highlightedSetting === name required property string name required property var object required property string setting @@ -13,6 +15,20 @@ Item { Layout.fillWidth: true Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2 + Rectangle { + anchors.fill: parent + anchors.margins: -Appearance.padding.smaller + color: DynamicColors.palette.m3primaryContainer + opacity: root.highlighted ? 0.5 : 0 + radius: Appearance.rounding.small + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.normal + } + } + } + RowLayout { id: row diff --git a/Modules/Settings/Controls/SettingsPage.qml b/Modules/Settings/Controls/SettingsPage.qml index 1cfefec..c9e1e74 100644 --- a/Modules/Settings/Controls/SettingsPage.qml +++ b/Modules/Settings/Controls/SettingsPage.qml @@ -9,6 +9,24 @@ CustomFlickable { default property alias contentData: clayout.data + // Find and scroll to a section by its sectionId, then highlight a specific setting + function scrollToSectionAndHighlight(sectionId: string, settingName: string) { + // Find the section with matching sectionId + for (let i = 0; i < clayout.children.length; i++) { + const section = clayout.children[i]; + if (section.sectionId === sectionId) { + // Scroll to the section with some padding + const targetY = section.y - Appearance.padding.normal; + contentY = Math.max(0, Math.min(targetY, contentHeight - height)); + + // Use the singleton to highlight the setting + SettingsHighlight.highlight(settingName); + return true; + } + } + return false; + } + contentHeight: clayout.implicitHeight TapHandler { diff --git a/Modules/Settings/Controls/SettingsSection.qml b/Modules/Settings/Controls/SettingsSection.qml index 081447a..8cdb049 100644 --- a/Modules/Settings/Controls/SettingsSection.qml +++ b/Modules/Settings/Controls/SettingsSection.qml @@ -8,6 +8,7 @@ CustomRect { default property alias contentData: layout.data property real contentPadding: Appearance.padding.large + property string sectionId: "" Layout.fillWidth: true Layout.preferredHeight: layout.implicitHeight + contentPadding * 2 diff --git a/Modules/Settings/SettingsIndex.mjs b/Modules/Settings/SettingsIndex.mjs new file mode 100644 index 0000000..b427758 --- /dev/null +++ b/Modules/Settings/SettingsIndex.mjs @@ -0,0 +1,1136 @@ +// Settings search index +// Each entry contains: name, category, categoryName, section, keywords +// - name: The display name of the setting (must match exactly) +// - category: The category key used for navigation +// - categoryName: Human-readable category name for display +// - section: The section header within the category +// - keywords: Additional search terms for better discoverability + +export const settingsIndex = [ + // GENERAL CATEGORY + // General section + { + name: "Logo", + category: "general", + categoryName: "General", + section: "General", + keywords: ["branding", "icon", "image"], + }, + { + name: "Wallpaper path", + category: "general", + categoryName: "General", + section: "General", + keywords: ["background", "image", "path"], + }, + { + name: "Desktop icons", + category: "general", + categoryName: "General", + section: "General", + keywords: ["icons", "desktop", "show"], + }, + // Color section + { + name: "Scheme mode", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["dark mode", "light mode", "theme", "color scheme"], + }, + { + name: "Scheme type", + category: "general", + categoryName: "General", + section: "Color", + keywords: [ + "vibrant", + "expressive", + "monochrome", + "neutral", + "tonal", + "fidelity", + "content", + "rainbow", + "fruit salad", + ], + }, + { + name: "Automatic color scheme", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["auto", "wallpaper", "generate", "color"], + }, + { + name: "Smart color scheme", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["intelligent", "adaptive", "color"], + }, + { + name: "Schedule dark mode", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["dark mode", "night", "time", "schedule", "automatic"], + }, + { + name: "Schedule Hyprsunset", + category: "general", + categoryName: "General", + section: "Color", + keywords: ["night light", "blue light", "temperature", "warm", "schedule"], + }, + // Default Apps section + { + name: "Terminal", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["console", "command", "shell", "default app"], + }, + { + name: "Audio", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["sound", "volume", "mixer", "default app"], + }, + { + name: "Playback", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["media", "player", "music", "video", "default app"], + }, + { + name: "Explorer", + category: "general", + categoryName: "General", + section: "Default Apps", + keywords: ["file manager", "files", "browser", "default app"], + }, + + // WALLPAPER CATEGORY + { + name: "Enable wallpaper rendering", + category: "wallpaper", + categoryName: "Wallpaper", + section: "Wallpaper", + keywords: ["background", "enable", "show"], + }, + { + name: "Fade duration", + category: "wallpaper", + categoryName: "Wallpaper", + section: "Wallpaper", + keywords: ["transition", "animation", "crossfade"], + }, + + // BAR CATEGORY + // Bar section + { + name: "Auto hide", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["hide", "visibility", "autohide", "panel"], + }, + { + name: "Height", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["size", "panel", "thickness"], + }, + { + name: "Rounding", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["corners", "radius", "rounded"], + }, + { + name: "Border", + category: "bar", + categoryName: "Bar", + section: "Bar", + keywords: ["outline", "stroke", "edge"], + }, + // Popouts section + { + name: "Tray", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["system tray", "icons", "popout"], + }, + { + name: "Audio", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["sound", "volume", "popout"], + }, + { + name: "Active window", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["window title", "current", "popout"], + }, + { + name: "Resources", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["cpu", "memory", "usage", "popout"], + }, + { + name: "Clock", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["time", "date", "popout"], + }, + { + name: "Network", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["wifi", "internet", "connection", "popout"], + }, + { + name: "Power", + category: "bar", + categoryName: "Bar", + section: "Popouts", + keywords: ["battery", "upower", "charging", "popout"], + }, + // Entries section + { + name: "Bar entries", + category: "bar", + categoryName: "Bar", + section: "Entries", + keywords: ["modules", "widgets", "items", "order"], + }, + // Dock section + { + name: "Enable dock", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["taskbar", "show", "visibility"], + }, + { + name: "Dock height", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["size", "taskbar", "thickness"], + }, + { + name: "Hover to reveal", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["autohide", "mouse", "show"], + }, + { + name: "Pin on startup", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["always visible", "pinned", "show"], + }, + { + name: "Pinned apps", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["favorites", "applications", "shortcuts"], + }, + { + name: "Ignored app regexes", + category: "bar", + categoryName: "Bar", + section: "Dock", + keywords: ["filter", "exclude", "hide", "regex"], + }, + + // LOCKSCREEN CATEGORY + { + name: "Recolor logo", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["logo", "color", "tint"], + }, + { + name: "Enable fingerprint", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["fprint", "biometric", "unlock"], + }, + { + name: "Max fingerprint tries", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["attempts", "limit", "fprint"], + }, + { + name: "Blur amount", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["background", "blur", "effect"], + }, + { + name: "Height multiplier", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["size", "scale", "height"], + }, + { + name: "Aspect ratio", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["ratio", "width", "height"], + }, + { + name: "Center width", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Lockscreen", + keywords: ["size", "width", "center"], + }, + { + name: "Idle Monitors", + category: "lockscreen", + categoryName: "Lockscreen", + section: "Idle", + keywords: ["timeout", "sleep", "idle", "lock", "suspend"], + }, + + // SERVICES CATEGORY + // Services section + { + name: "Weather location", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["city", "location", "weather"], + }, + { + name: "Use Fahrenheit", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["temperature", "celsius", "units"], + }, + { + name: "Use twelve hour clock", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["time", "format", "12h", "24h", "am pm"], + }, + { + name: "Enable ddcutil service", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["monitor", "brightness", "ddc"], + }, + { + name: "GPU type", + category: "services", + categoryName: "Services", + section: "Services", + keywords: ["graphics", "nvidia", "amd", "intel"], + }, + // Media section + { + name: "Audio increment", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["volume", "step", "increment"], + }, + { + name: "Brightness increment", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["screen", "step", "increment"], + }, + { + name: "Max volume", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["audio", "limit", "maximum"], + }, + { + name: "Default player", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["music", "media", "mpris"], + }, + { + name: "Visualizer bars", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["audio", "visualization", "cava"], + }, + { + name: "Player aliases", + category: "services", + categoryName: "Services", + section: "Media", + keywords: ["mpris", "rename", "alias"], + }, + + // NOTIFICATIONS CATEGORY + // Notifications section + { + name: "Expire notifications", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["auto dismiss", "timeout", "expire"], + }, + { + name: "Default expire timeout", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["duration", "time", "dismiss"], + }, + { + name: "App notification cooldown", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["rate limit", "spam", "cooldown"], + }, + { + name: "Clear threshold", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["swipe", "dismiss", "threshold"], + }, + { + name: "Expand threshold", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["swipe", "expand", "threshold"], + }, + { + name: "Action on click", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["click", "action", "default"], + }, + { + name: "Group preview count", + category: "notifications", + categoryName: "Notifications", + section: "Notifications", + keywords: ["group", "stack", "preview"], + }, + // Sizes section + { + name: "Width", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "size", "width"], + }, + { + name: "Image size", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "image", "icon"], + }, + { + name: "Badge size", + category: "notifications", + categoryName: "Notifications", + section: "Sizes", + keywords: ["notification", "badge", "app icon"], + }, + + // SIDEBAR CATEGORY + { + name: "Enable sidebar", + category: "sidebar", + categoryName: "Sidebar", + section: "Sidebar", + keywords: ["show", "panel", "side"], + }, + { + name: "Width", + category: "sidebar", + categoryName: "Sidebar", + section: "Sidebar", + keywords: ["size", "panel", "width"], + }, + + // UTILITIES CATEGORY + // Utilities section + { + name: "Enable utilities", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["show", "enable"], + }, + { + name: "Max toasts", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["notifications", "limit", "maximum"], + }, + { + name: "Panel width", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["size", "width"], + }, + { + name: "Toast width", + category: "utilities", + categoryName: "Utilities", + section: "Utilities", + keywords: ["notification", "size", "width"], + }, + // Toasts section + { + name: "Config loaded", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "config"], + }, + { + name: "Charging changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "battery", "power"], + }, + { + name: "Game mode changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "gaming"], + }, + { + name: "Do not disturb changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "dnd", "quiet"], + }, + { + name: "Audio output changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "speaker", "headphones"], + }, + { + name: "Audio input changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "microphone"], + }, + { + name: "Caps lock changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "keyboard"], + }, + { + name: "Num lock changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "keyboard"], + }, + { + name: "Keyboard layout changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "language", "input"], + }, + { + name: "VPN changed", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "vpn", "network"], + }, + { + name: "Now playing", + category: "utilities", + categoryName: "Utilities", + section: "Toasts", + keywords: ["toast", "notification", "music", "media"], + }, + // VPN section + { + name: "Enable VPN integration", + category: "utilities", + categoryName: "Utilities", + section: "VPN", + keywords: ["vpn", "network", "connection"], + }, + { + name: "Provider", + category: "utilities", + categoryName: "Utilities", + section: "VPN", + keywords: ["vpn", "service", "provider"], + }, + + // DASHBOARD CATEGORY + // Dashboard section + { + name: "Enable dashboard", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["show", "enable", "panel"], + }, + { + name: "Media update interval", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["refresh", "interval", "music"], + }, + { + name: "Resource update interval", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["refresh", "interval", "cpu", "memory"], + }, + { + name: "Drag threshold", + category: "dashboard", + categoryName: "Dashboard", + section: "Dashboard", + keywords: ["swipe", "gesture", "threshold"], + }, + // Performance section + { + name: "Show battery", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["power", "battery", "visibility"], + }, + { + name: "Show GPU", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["graphics", "gpu", "visibility"], + }, + { + name: "Show CPU", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["processor", "cpu", "visibility"], + }, + { + name: "Show memory", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["ram", "memory", "visibility"], + }, + { + name: "Show storage", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["disk", "storage", "visibility"], + }, + { + name: "Show network", + category: "dashboard", + categoryName: "Dashboard", + section: "Performance", + keywords: ["internet", "network", "visibility"], + }, + // Layout Sizes section (read-only) + { + name: "Tab indicator height", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "tab", "indicator"], + }, + { + name: "Tab indicator spacing", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "tab", "spacing"], + }, + { + name: "Info width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "info", "width"], + }, + { + name: "Info icon size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "icon"], + }, + { + name: "Date time width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "clock", "date"], + }, + { + name: "Media width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "music", "player"], + }, + { + name: "Media progress sweep", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "arc"], + }, + { + name: "Media progress thickness", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "stroke"], + }, + { + name: "Resource progress thickness", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "progress", "stroke"], + }, + { + name: "Weather width", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "weather"], + }, + { + name: "Media cover art size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "album", "artwork"], + }, + { + name: "Media visualiser size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "visualizer", "cava"], + }, + { + name: "Resource size", + category: "dashboard", + categoryName: "Dashboard", + section: "Layout Sizes", + keywords: ["size", "cpu", "memory"], + }, + + // APPEARANCE CATEGORY + // Scale section + { + name: "Rounding scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["corners", "radius", "scale"], + }, + { + name: "Spacing scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["gap", "margin", "scale"], + }, + { + name: "Padding scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["padding", "inset", "scale"], + }, + { + name: "Font size scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["text", "font", "scale"], + }, + { + name: "Animation duration scale", + category: "appearance", + categoryName: "Appearance", + section: "Scale", + keywords: ["animation", "speed", "duration"], + }, + // Fonts section + { + name: "Sans family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "typeface", "sans-serif"], + }, + { + name: "Monospace family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "typeface", "mono", "code"], + }, + { + name: "Material family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "icons", "material"], + }, + { + name: "Clock family", + category: "appearance", + categoryName: "Appearance", + section: "Fonts", + keywords: ["font", "time", "clock"], + }, + // Animation section + { + name: "Media GIF speed adjustment", + category: "appearance", + categoryName: "Appearance", + section: "Animation", + keywords: ["gif", "speed", "animation"], + }, + { + name: "Session GIF speed", + category: "appearance", + categoryName: "Appearance", + section: "Animation", + keywords: ["gif", "speed", "animation", "login"], + }, + // Transparency section + { + name: "Enable transparency", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "translucent", "blur"], + }, + { + name: "Base opacity", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "alpha", "transparency"], + }, + { + name: "Layer opacity", + category: "appearance", + categoryName: "Appearance", + section: "Transparency", + keywords: ["opacity", "alpha", "layers"], + }, + + // OSD CATEGORY + // On Screen Display section + { + name: "Enable OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["show", "enable", "overlay"], + }, + { + name: "Hide delay", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["timeout", "duration", "dismiss"], + }, + { + name: "Enable brightness OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["screen", "brightness", "overlay"], + }, + { + name: "Enable microphone OSD", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["mic", "audio", "overlay"], + }, + { + name: "Brightness on all monitors", + category: "osd", + categoryName: "On screen display", + section: "On Screen Display", + keywords: ["multi-monitor", "all screens"], + }, + // Sizes section + { + name: "Slider width", + category: "osd", + categoryName: "On screen display", + section: "Sizes", + keywords: ["size", "osd", "width"], + }, + { + name: "Slider height", + category: "osd", + categoryName: "On screen display", + section: "Sizes", + keywords: ["size", "osd", "height"], + }, + + // LAUNCHER CATEGORY + // Launcher section + { + name: "Max apps shown", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["limit", "apps", "search results"], + }, + { + name: "Max wallpapers shown", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["limit", "wallpapers", "search results"], + }, + { + name: "Action prefix", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["command", "prefix", "action"], + }, + { + name: "Special prefix", + category: "launcher", + categoryName: "Launcher", + section: "Launcher", + keywords: ["command", "prefix", "special"], + }, + // Fuzzy Search section + { + name: "Apps", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "applications"], + }, + { + name: "Actions", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "actions"], + }, + { + name: "Schemes", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "color schemes"], + }, + { + name: "Variants", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "variants"], + }, + { + name: "Wallpapers", + category: "launcher", + categoryName: "Launcher", + section: "Fuzzy Search", + keywords: ["fuzzy", "search", "backgrounds"], + }, + // Sizes section + { + name: "Item width", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "app", "width"], + }, + { + name: "Item height", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "app", "height"], + }, + { + name: "Wallpaper width", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "wallpaper", "width"], + }, + { + name: "Wallpaper height", + category: "launcher", + categoryName: "Launcher", + section: "Sizes", + keywords: ["size", "wallpaper", "height"], + }, + // Actions section + { + name: "Launcher actions", + category: "launcher", + categoryName: "Launcher", + section: "Actions", + keywords: ["commands", "shortcuts", "actions"], + }, +]; + +// Helper function to search with keywords first, then fuzzy fallback +export function searchSettings(query, fuzzyModule) { + if (!query || query.trim() === "") { + return []; + } + + const q = query.toLowerCase().trim(); + const results = []; + const seen = new Set(); + + // 1. Exact keyword match (highest priority) + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; + + for (const keyword of setting.keywords) { + if (keyword.toLowerCase() === q) { + results.push( + Object.assign({}, setting, { + matchType: "exact-keyword", + score: 1.0, + }), + ); + seen.add(key); + break; + } + } + } + + // 2. Partial keyword match + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; + + for (const keyword of setting.keywords) { + if (keyword.toLowerCase().includes(q)) { + results.push( + Object.assign({}, setting, { + matchType: "partial-keyword", + score: 0.8, + }), + ); + seen.add(key); + break; + } + } + } + + // 3. Name contains query + for (const setting of settingsIndex) { + const key = `${setting.category}:${setting.name}`; + if (seen.has(key)) continue; + + if (setting.name.toLowerCase().includes(q)) { + results.push( + Object.assign({}, setting, { matchType: "name-contains", score: 0.7 }), + ); + seen.add(key); + } + } + + // 4. Fuzzy match on name (fallback for typos) + if (fuzzyModule && results.length < 10) { + const fuzzyTargets = settingsIndex + .filter((s) => !seen.has(`${s.category}:${s.name}`)) + .map((s) => Object.assign({}, s, { _searchTarget: s.name })); + + if (fuzzyTargets.length > 0) { + const fuzzyResults = fuzzyModule.go(query, fuzzyTargets, { + key: "_searchTarget", + limit: 10 - results.length, + threshold: 0.3, + }); + + for (const r of fuzzyResults) { + const setting = r.obj; + results.push( + Object.assign({}, setting, { + matchType: "fuzzy", + score: r.score * 0.6, + _searchTarget: undefined, + }), + ); + } + } + } + + // Sort by score descending + results.sort((a, b) => b.score - a.score); + + return results; +} diff --git a/Modules/Settings/SettingsSearch.qml b/Modules/Settings/SettingsSearch.qml new file mode 100644 index 0000000..6fb977f --- /dev/null +++ b/Modules/Settings/SettingsSearch.qml @@ -0,0 +1,310 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import qs.Components +import qs.Config +import "../../scripts/fuzzysort.js" as Fuzzy +import "./SettingsIndex.mjs" as SettingsIndex + +Item { + id: root + + property alias text: searchField.text + + signal settingSelected(string category, string section, string settingName) + + function close() { + searchField.text = ""; + searchField.focus = false; + popup.close(); + } + + function search(query) { + resultsModel.clear(); + + if (!query || query.trim() === "") { + popup.close(); + return; + } + + const results = SettingsIndex.searchSettings(query, Fuzzy); + + for (const result of results.slice(0, 10)) { + resultsModel.append({ + name: result.name, + category: result.category, + categoryName: result.categoryName, + section: result.section, + matchType: result.matchType + }); + } + + if (resultsModel.count > 0) { + popup.open(); + } else { + popup.close(); + } + } + + implicitHeight: searchContainer.implicitHeight + implicitWidth: 200 + + ListModel { + id: resultsModel + + } + + CustomRect { + id: searchContainer + + anchors.fill: parent + color: DynamicColors.tPalette.m3surfaceContainerHigh + implicitHeight: searchRow.implicitHeight + Appearance.padding.small * 2 + radius: Appearance.rounding.full + + RowLayout { + id: searchRow + + anchors.fill: parent + anchors.leftMargin: Appearance.padding.normal + anchors.rightMargin: Appearance.padding.small + spacing: Appearance.spacing.small + + MaterialIcon { + Layout.alignment: Qt.AlignVCenter + color: DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.larger + text: "search" + } + + CustomTextField { + id: searchField + + Layout.alignment: Qt.AlignVCenter + Layout.fillWidth: true + font.pointSize: Appearance.font.size.small + placeholderText: qsTr("Search settings...") + + Keys.onDownPressed: { + if (popup.visible && resultsList.count > 0) { + resultsList.currentIndex = Math.min(resultsList.currentIndex + 1, resultsList.count - 1); + } + } + Keys.onEscapePressed: { + root.close(); + } + Keys.onReturnPressed: { + if (popup.visible && resultsList.currentIndex >= 0) { + const item = resultsModel.get(resultsList.currentIndex); + root.settingSelected(item.category, item.section, item.name); + root.close(); + } + } + Keys.onUpPressed: { + if (popup.visible && resultsList.count > 0) { + resultsList.currentIndex = Math.max(resultsList.currentIndex - 1, 0); + } + } + onTextChanged: { + searchTimer.restart(); + } + } + + // Clear button + IconButton { + Layout.alignment: Qt.AlignVCenter + font.pointSize: Appearance.font.size.larger + icon: "close" + opacity: searchField.text.length > 0 ? 1 : 0 + + Behavior on opacity { + Anim { + duration: Appearance.anim.durations.small + } + } + + onClicked: { + root.close(); + } + } + } + } + + // Debounce timer for search + Timer { + id: searchTimer + + interval: 150 + + onTriggered: root.search(searchField.text) + } + + // Results dropdown + Popup { + id: popup + + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + implicitHeight: Math.min(resultsList.contentHeight + Appearance.padding.small * 2 + 10, (contentItem.view.delegate.implicitHeight + Appearance.spacing.smaller) * 5 - Appearance.spacing.smaller + 10) + implicitWidth: root.width + Appearance.padding.small * 2 + modal: false + padding: 0 + x: root.width / 2 - popup.width / 2 + y: searchContainer.height + Appearance.spacing.small + + background: Item { + } + contentItem: Item { + property alias view: resultsList + + Elevation { + id: popupShadow + + anchors.centerIn: parent + height: popup.implicitHeight - 10 + level: 2 + radius: Appearance.rounding.normal + width: popup.implicitWidth - 10 + + CustomRect { + anchors.fill: parent + color: DynamicColors.palette.m3surfaceContainer + radius: Appearance.rounding.normal + + CustomListView { + id: resultsList + + anchors.fill: parent + anchors.margins: Appearance.padding.small + clip: true + currentIndex: 0 + highlightFollowsCurrentItem: false + highlightRangeMode: ListView.ApplyRange + model: resultsModel + preferredHighlightBegin: 0 + preferredHighlightEnd: height + spacing: Appearance.spacing.smaller + + delegate: SearchResultItem { + required property string category + required property string categoryName + required property int index + required property string matchType + required property string name + required property string section + + highlighted: index === resultsList.currentIndex + width: resultsList.width + + onClicked: { + root.settingSelected(category, section, name); + root.close(); + } + } + highlight: CustomRect { + color: DynamicColors.palette.m3primary + implicitHeight: resultsList.currentItem?.implicitHeight ?? 0 + implicitWidth: resultsList.width + radius: Appearance.rounding.normal - Appearance.padding.smaller + y: resultsList.currentItem?.y ?? 0 + + Behavior on y { + Anim { + duration: Appearance.anim.durations.small + easing.bezierCurve: Appearance.anim.curves.expressiveEffects + } + } + } + } + } + } + } + enter: Transition { + Anim { + duration: Appearance.anim.durations.small + from: 0 + property: "opacity" + to: 1 + } + + Anim { + duration: Appearance.anim.durations.small + from: 0.95 + property: "scale" + to: 1.0 + } + } + exit: Transition { + Anim { + duration: Appearance.anim.durations.smaller + from: 1 + property: "opacity" + to: 0 + } + } + } + + // Search result item component + component SearchResultItem: CustomRect { + id: resultItem + + property bool highlighted: false + + signal clicked + + implicitHeight: resultLayout.implicitHeight + Appearance.padding.small * 2 + radius: Appearance.rounding.small + + ColumnLayout { + id: resultLayout + + anchors.fill: parent + anchors.margins: Appearance.padding.small + spacing: 2 + + // Setting name + CustomText { + Layout.fillWidth: true + color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface + elide: Text.ElideRight + font.pointSize: Appearance.font.size.small + font.weight: Font.Medium + text: resultItem.name + } + + // Category and section path + RowLayout { + Layout.fillWidth: true + spacing: Appearance.spacing.smaller + + CustomText { + color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.smaller + opacity: 0.8 + text: resultItem.categoryName + } + + CustomText { + color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant + font.pointSize: Appearance.font.size.smaller + opacity: 0.6 + text: "›" + } + + CustomText { + Layout.fillWidth: true + color: highlighted ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurfaceVariant + elide: Text.ElideRight + font.pointSize: Appearance.font.size.smaller + opacity: 0.8 + text: resultItem.section + } + } + } + + StateLayer { + onClicked: resultItem.clicked() + } + } +} diff --git a/nix/default.nix b/nix/default.nix index b2cbc8a..acf66ba 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -119,12 +119,14 @@ stdenv.mkDerivation { ++ cmakeVersionFlags; prePatch = '' - substituteInPlace shell.qml \ - --replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false' - substituteInPlace Helpers/Hyprsunset.qml \ - --replace-fail 'Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${temp}`]);' 'Quickshell.execDetached(["hyprsunset", "-t", `${temp}`]);' - substituteInPlace Helpers/Hyprsunset.qml \ - --replace-fail 'Quickshell.execDetached(["hyprctl", "hyprsunset", "identity"]);' 'Quickshell.execDetached(["hyprsunset", "--identity"]);' + substituteInPlace shell.qml \ + --replace-fail 'ShellRoot {' 'ShellRoot { settings.watchFiles: false' + substituteInPlace Helpers/Hyprsunset.qml \ + --replace-fail 'Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `''${temp}`]);' \ + 'Quickshell.execDetached(["hyprsunset", "-t", `''${temp}`]);' + substituteInPlace Helpers/Hyprsunset.qml \ + --replace-fail 'Quickshell.execDetached(["hyprctl", "hyprsunset", "identity"]);' \ + 'Quickshell.execDetached(["hyprsunset", "--identity"]);' ''; postInstall = ''