From 9043ae7bf61d51167969c659ab8c7acb5c8b1f1b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 5 Dec 2025 00:19:21 +0100 Subject: [PATCH] waffles: functioning search --- .../icons/fluent/app-generic-filled.svg | 4 + .../ii/assets/icons/fluent/app-generic.svg | 4 + .../icons/fluent/arrow-enter-left-filled.svg | 4 + .../assets/icons/fluent/arrow-enter-left.svg | 4 + .../assets/icons/fluent/calculator-filled.svg | 4 + .../ii/assets/icons/fluent/calculator.svg | 4 + .../ii/assets/icons/fluent/desktop-filled.svg | 4 + .../ii/assets/icons/fluent/desktop.svg | 4 + .../icons/fluent/globe-search-filled.svg | 4 + .../ii/assets/icons/fluent/globe-search.svg | 4 + .../assets/icons/fluent/image-copy-filled.svg | 4 + .../ii/assets/icons/fluent/image-copy.svg | 4 + .../ii/assets/icons/fluent/image-filled.svg | 4 + .../ii/assets/icons/fluent/image.svg | 4 + .../ii/assets/icons/fluent/library-filled.svg | 4 + .../ii/assets/icons/fluent/library.svg | 4 + .../ii/assets/icons/fluent/news-filled.svg | 4 + .../ii/assets/icons/fluent/news.svg | 4 + .../ii/assets/icons/fluent/open-filled.svg | 4 + .../ii/assets/icons/fluent/open.svg | 4 + .../ii/assets/icons/fluent/people-filled.svg | 4 + .../icons/fluent/people-team-filled.svg | 4 + .../ii/assets/icons/fluent/people-team.svg | 4 + .../ii/assets/icons/fluent/people.svg | 4 + .../ii/assets/icons/fluent/record-filled.svg | 4 + .../ii/assets/icons/fluent/record.svg | 1 + .../ii/assets/icons/fluent/server-filled.svg | 4 + .../ii/assets/icons/fluent/server.svg | 4 + .../icons/fluent/store-microsoft-filled.svg | 4 + .../assets/icons/fluent/store-microsoft.svg | 4 + .../ii/assets/icons/fluent/wand-filled.svg | 4 + .../ii/assets/icons/fluent/wand.svg | 4 + .../common/models/LauncherSearchResult.qml | 5 +- .../common/widgets/StyledScrollBar.qml | 1 + .../ii/modules/waffle/looks/Looks.qml | 1 + .../ii/modules/waffle/looks/WIcons.qml | 61 ++++- .../ii/modules/waffle/looks/WListView.qml | 10 + .../ii/modules/waffle/looks/WScrollBar.qml | 25 +++ .../ii/modules/waffle/startMenu/SearchBar.qml | 12 +- .../waffle/startMenu/SearchEntryIcon.qml | 48 ++++ .../waffle/startMenu/SearchPageContent.qml | 4 + .../waffle/startMenu/SearchResults.qml | 208 ++++++++++++++++-- .../waffle/startMenu/StartMenuContent.qml | 72 +++++- .../waffle/startMenu/StartMenuContext.qml | 64 ++++++ .../ii/modules/waffle/startMenu/TagStrip.qml | 38 +--- .../waffle/startMenu/WSearchResultButton.qml | 50 ++--- .../waffle/startMenu/WaffleStartMenu.qml | 5 +- .../quickshell/ii/services/LauncherSearch.qml | 7 +- 48 files changed, 648 insertions(+), 88 deletions(-) create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/app-generic-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/app-generic.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/calculator-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/calculator.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/desktop-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/desktop.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/globe-search-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/globe-search.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/image-copy-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/image-copy.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/image-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/image.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/library-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/library.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/news-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/news.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/open-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/open.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/people-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/people-team-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/people-team.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/people.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/record-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/record.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/server-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/server.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/wand-filled.svg create mode 100644 dots/.config/quickshell/ii/assets/icons/fluent/wand.svg create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml create mode 100644 dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/app-generic-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/app-generic-filled.svg new file mode 100644 index 000000000..87c7b6c41 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/app-generic-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/app-generic.svg b/dots/.config/quickshell/ii/assets/icons/fluent/app-generic.svg new file mode 100644 index 000000000..cd0c7b516 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/app-generic.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left-filled.svg new file mode 100644 index 000000000..f183cc835 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left.svg b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left.svg new file mode 100644 index 000000000..c9ffd3eda --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/arrow-enter-left.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/calculator-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/calculator-filled.svg new file mode 100644 index 000000000..92ad06215 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/calculator-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/calculator.svg b/dots/.config/quickshell/ii/assets/icons/fluent/calculator.svg new file mode 100644 index 000000000..0dc9d3056 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/calculator.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/desktop-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-filled.svg new file mode 100644 index 000000000..d09661aec --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/desktop-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/desktop.svg b/dots/.config/quickshell/ii/assets/icons/fluent/desktop.svg new file mode 100644 index 000000000..979d35cc3 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/desktop.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/globe-search-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/globe-search-filled.svg new file mode 100644 index 000000000..933c6282d --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/globe-search-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/globe-search.svg b/dots/.config/quickshell/ii/assets/icons/fluent/globe-search.svg new file mode 100644 index 000000000..caa2c94ff --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/globe-search.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image-copy-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image-copy-filled.svg new file mode 100644 index 000000000..f01cd9872 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image-copy-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image-copy.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image-copy.svg new file mode 100644 index 000000000..a40940693 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image-copy.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image-filled.svg new file mode 100644 index 000000000..d35954106 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/image.svg b/dots/.config/quickshell/ii/assets/icons/fluent/image.svg new file mode 100644 index 000000000..379a1cd14 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/image.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/library-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/library-filled.svg new file mode 100644 index 000000000..7afac3d5e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/library-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/library.svg b/dots/.config/quickshell/ii/assets/icons/fluent/library.svg new file mode 100644 index 000000000..79432ebdd --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/library.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/news-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/news-filled.svg new file mode 100644 index 000000000..1d07cf802 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/news-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/news.svg b/dots/.config/quickshell/ii/assets/icons/fluent/news.svg new file mode 100644 index 000000000..9e0c3a2fe --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/news.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/open-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/open-filled.svg new file mode 100644 index 000000000..95ae58d39 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/open-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/open.svg b/dots/.config/quickshell/ii/assets/icons/fluent/open.svg new file mode 100644 index 000000000..561ed5ec2 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/open.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/people-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/people-filled.svg new file mode 100644 index 000000000..96e98b863 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/people-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/people-team-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/people-team-filled.svg new file mode 100644 index 000000000..84364ec4e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/people-team-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/people-team.svg b/dots/.config/quickshell/ii/assets/icons/fluent/people-team.svg new file mode 100644 index 000000000..8507e971c --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/people-team.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/people.svg b/dots/.config/quickshell/ii/assets/icons/fluent/people.svg new file mode 100644 index 000000000..967192214 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/people.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/record-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/record-filled.svg new file mode 100644 index 000000000..164d37e87 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/record-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/record.svg b/dots/.config/quickshell/ii/assets/icons/fluent/record.svg new file mode 100644 index 000000000..31e957588 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/record.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/server-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/server-filled.svg new file mode 100644 index 000000000..2b0869653 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/server-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/server.svg b/dots/.config/quickshell/ii/assets/icons/fluent/server.svg new file mode 100644 index 000000000..1b876a172 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/server.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft-filled.svg new file mode 100644 index 000000000..6673448f3 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft.svg b/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft.svg new file mode 100644 index 000000000..affc6cc6e --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/store-microsoft.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/wand-filled.svg b/dots/.config/quickshell/ii/assets/icons/fluent/wand-filled.svg new file mode 100644 index 000000000..fffb70e7f --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/wand-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/assets/icons/fluent/wand.svg b/dots/.config/quickshell/ii/assets/icons/fluent/wand.svg new file mode 100644 index 000000000..a5033c274 --- /dev/null +++ b/dots/.config/quickshell/ii/assets/icons/fluent/wand.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/common/models/LauncherSearchResult.qml b/dots/.config/quickshell/ii/modules/common/models/LauncherSearchResult.qml index 15beef12e..c47fc4f39 100644 --- a/dots/.config/quickshell/ii/modules/common/models/LauncherSearchResult.qml +++ b/dots/.config/quickshell/ii/modules/common/models/LauncherSearchResult.qml @@ -19,11 +19,14 @@ QtObject { } property var actions: [] - // Stuff needed for DesktopEntry objects + // Stuff needed for DesktopEntry + property string id: "" property bool shown: true property string comment: "" property bool runInTerminal: false property string genericName: "" property list keywords: [] + // Extra stuff to allow for more flexibility + property string category: type } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml index 7b677c426..f37203250 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml @@ -9,6 +9,7 @@ ScrollBar { policy: ScrollBar.AsNeeded topPadding: Appearance.rounding.normal bottomPadding: Appearance.rounding.normal + active: hovered || pressed contentItem: Rectangle { implicitWidth: 4 diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml index b9cbc00bd..af656d8d6 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml @@ -152,6 +152,7 @@ Singleton { property real normal: 11 property real large: 13 property real larger: 15 + property real xlarger: 17 } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml index 7fe121165..b804aa3d9 100644 --- a/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml @@ -48,7 +48,7 @@ Singleton { } property string batteryLevelIcon: { - const discreteLevel = Math.ceil(Battery.percentage * 10) + const discreteLevel = Math.ceil(Battery.percentage * 10); return `battery-${discreteLevel > 9 ? "full" : discreteLevel}`; } @@ -107,7 +107,8 @@ Singleton { function audioAppIcon(node) { let icon; icon = AppSearch.guessIcon(node?.properties["application.icon-name"] ?? ""); - if (AppSearch.iconExists(icon)) return icon; + if (AppSearch.iconExists(icon)) + return icon; icon = AppSearch.guessIcon(node?.properties["node.name"] ?? ""); return icon; } @@ -127,4 +128,60 @@ Singleton { return "bluetooth"; } + function fluentFromMaterial(icon) { + switch (icon) { + case "calculate": + return "calculator"; + case "keyboard_return": + return "arrow-enter-left"; + case "open_in_new": + return "open"; + case "settings_suggest": + return "wand"; + case "terminal": + return "app-generic"; + case "travel_explore": + return "globe-search"; + case "keep": + return "pin"; + case "keep_off": + return "pin-off"; + default: + return "apps"; + } + } + + function guessIconForName(name) { + const lowerName = name.toLowerCase(); + if (lowerName.includes("app") || lowerName.includes("desktop")) + return "apps"; + if (lowerName.includes("news")) + return "news"; + if (lowerName.includes("new") || lowerName.includes("create") || lowerName.includes("add")) + return "add"; + if (lowerName.includes("open")) + return "open"; + if (lowerName.includes("friends") || lowerName.includes("contact") || lowerName.includes("family")) + return "people"; + if (lowerName.includes("community")) + return "people-team"; + if (lowerName.includes("library")) + return "library"; + if (lowerName.includes("setting")) + return "settings"; + if (lowerName.includes("gallery")) + return "image-copy"; + if (lowerName.includes("server")) + return "server"; + if (lowerName.includes("picture") || lowerName.includes("photo") || lowerName.includes("image")) + return "image"; + if (lowerName.includes("store") || lowerName.includes("shop")) + return "store-microsoft"; + if (lowerName.includes("record") || lowerName.includes("capture")) + return "record"; + if (lowerName.includes("screen") || lowerName.includes("display") || lowerName.includes("monitor") || lowerName.includes("desktop")) + return "desktop"; + + return "apps"; + } } diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml new file mode 100644 index 000000000..bc567762a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml @@ -0,0 +1,10 @@ +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import QtQuick.Controls + +ListView { + id: root + + ScrollBar.vertical: WScrollBar {} +} diff --git a/dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml b/dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml new file mode 100644 index 000000000..bdc962806 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml @@ -0,0 +1,25 @@ +import QtQuick +import QtQuick.Controls +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions + +ScrollBar { + id: root + + policy: ScrollBar.AsNeeded + active: hovered || pressed + property color color: Looks.colors.controlBg + + contentItem: Rectangle { + implicitWidth: root.active ? 4 : 2 + implicitHeight: root.visualSize + radius: 9999 + color: root.color + + opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0 + Behavior on opacity { + animation: Looks.transition.opacity.createObject(this) + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml index ce5652329..d38bc8d98 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml @@ -15,10 +15,16 @@ FooterRectangle { property real horizontalPadding: 32 property real verticalPadding: 16 property bool searching: text.length > 0 + property alias searchInput: searchInput property alias text: searchInput.text implicitHeight: outline.implicitHeight + verticalPadding * 2 - Component.onCompleted: searchInput.forceActiveFocus() + signal accepted() + + Component.onCompleted: forceFocus() + function forceFocus() { + searchInput.forceActiveFocus(); + } focus: true color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter @@ -81,6 +87,10 @@ FooterRectangle { visible: searchInput.text.length === 0 font.pixelSize: Looks.font.pixelSize.large } + + onAccepted: { + root.accepted(); + } } } } diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml new file mode 100644 index 000000000..5f94870b7 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchEntryIcon.qml @@ -0,0 +1,48 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs +import qs.services +import qs.modules.common +import qs.modules.common.models +import qs.modules.common.functions +import qs.modules.common.widgets +import qs.modules.waffle.looks + +Item { + id: root + required property LauncherSearchResult entry + property int iconSize: 24 + implicitWidth: Math.max(iconSize, textIconLoader.implicitWidth) + implicitHeight: iconSize + Loader { + anchors.centerIn: parent + active: root.entry.iconType === LauncherSearchResult.IconType.System && root.entry.iconName !== "" + sourceComponent: WAppIcon { + implicitSize: root.iconSize + iconName: root.entry.iconName + tryCustomIcon: false + animated: false + } + } + Loader { + id: textIconLoader + anchors.centerIn: parent + active: root.entry.iconType === LauncherSearchResult.IconType.Text + sourceComponent: WText { + text: root.entry.iconName + font.pixelSize: root.iconSize + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + Loader { + anchors.centerIn: parent + active: root.entry.iconType === LauncherSearchResult.IconType.Material || root.entry.iconType === LauncherSearchResult.IconType.None || root.entry.iconName === "" + sourceComponent: FluentIcon { + icon: root.entry.iconName ? WIcons.fluentFromMaterial(root.entry.iconName) : WIcons.guessIconForName(root.entry.name) + implicitSize: root.iconSize + animated: false + } + } +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml index a294950fd..b6f4a4e94 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchPageContent.qml @@ -12,7 +12,9 @@ import qs.modules.waffle.looks BodyRectangle { id: root + property alias context: searchResults.context property string searchText: LauncherSearch.query + property alias currentIndex: searchResults.currentIndex ColumnLayout { anchors { @@ -24,11 +26,13 @@ BodyRectangle { spacing: 12 TagStrip { + context: root.context Layout.fillWidth: true Layout.fillHeight: false } SearchResults { + id: searchResults Layout.fillWidth: true Layout.fillHeight: true } diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml index 5f3d93411..14627e54a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/SearchResults.qml @@ -1,19 +1,44 @@ +pragma ComponentBehavior: Bound +import qs +import qs.services +import qs.modules.common import qs.modules.waffle.looks import qs.modules.common.functions -import qs.modules.common -import qs.services -import qs +import qs.modules.common.models import Quickshell import QtQuick.Layouts import QtQuick.Controls import QtQuick -pragma ComponentBehavior: Bound RowLayout { id: root + property int maxResultsPerCategory: 4 + property StartMenuContext context + property int currentIndex: context.currentIndex + onCurrentIndexChanged: { + forceCurrentIndex(currentIndex); + } function focusFirstItem() { - resultList.currentIndex = 0; + forceCurrentIndex(0); + } + function forceCurrentIndex(index) { + context.currentIndex = index; + // Somehow this hack is needed + if (index === 0) { + resultList.incrementCurrentIndex(); + resultList.decrementCurrentIndex(); + } else { + resultList.decrementCurrentIndex(); + resultList.incrementCurrentIndex(); + } + } + + Connections { + target: context + function onAccepted() { + resultList.currentItem?.execute(); + } } ResultList { @@ -25,23 +50,74 @@ RowLayout { Layout.preferredWidth: 386 Layout.leftMargin: 1 Layout.rightMargin: 1 + entry: resultList.model[resultList.currentIndex] ?? searchResultComp.createObject() } - component ResultList: ListView { + component ResultList: WListView { + id: resultListView section { criteria: ViewSection.FullString - property: "type" + property: "category" // This is "type" with tweaks to make it match more closely + labelPositioning: ViewSection.InlineLabels + delegate: Item { + id: sectionButton + required property string section + implicitHeight: sectionChoiceButton.implicitHeight + resultListView.spacing + width: ListView.view?.width + WChoiceButton { + id: sectionChoiceButton + anchors { + left: parent.left + right: parent.right + top: parent.top + } + implicitHeight: 38 + contentItem: WText { + text: sectionButton.section + font.pixelSize: Looks.font.pixelSize.large + font.weight: Looks.font.weight.strong + } + onClicked: { + root.context.selectCategory(sectionButton.section); + } + } + } } clip: true spacing: 4 - model: ScriptModel { - values: { - // TODO: categorize and have max per category - LauncherSearch.results.slice(0, 10) - } - onValuesChanged: { - root.focusFirstItem(); + currentIndex: root.currentIndex + + // We can't use a ScriptModel here because it would mess up sections + model: { + const allResults = LauncherSearch.results; + // Find categories + var categories = new Set(); + for (let i = 0; i < allResults.length; i++) { + categories.add(allResults[i].type); } + + // Collect max 4 per category + var categorizedResults = []; + categories.forEach(category => { + let count = 0; + for (let i = 0; i < allResults.length; i++) { + if (allResults[i].type === category) { + const entry = allResults[i]; + const tweakedEntry = searchResultComp.createObject(null, Object.assign({}, entry)); + tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr("Best match") : entry.type + categorizedResults.push(tweakedEntry); // Section header + count++; + if (count >= root.maxResultsPerCategory) { + break; + } + } + } + }); + // print(JSON.stringify(categorizedResults, null, 2)); + return categorizedResults; + } + onModelChanged: { + root.focusFirstItem(); } delegate: WSearchResultButton { required property int index @@ -53,8 +129,112 @@ RowLayout { } component ResultPreview: Rectangle { + id: resultPreview + + property LauncherSearchResult entry // LauncherSearchResult + Layout.fillHeight: true color: Looks.colors.bg1 radius: Looks.radius.large + + ColumnLayout { + anchors.fill: parent + anchors.margins: 22 + spacing: 13 + + ColumnLayout { + id: mainInfoColumn + Layout.alignment: Qt.AlignHCenter + SearchEntryIcon { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 10 + Layout.bottomMargin: 12 + entry: resultPreview.entry + iconSize: 64 + } + WText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + text: resultPreview.entry?.name || "" + font.pixelSize: Looks.font.pixelSize.xlarger + } + WText { + Layout.alignment: Qt.AlignHCenter + text: resultPreview.entry?.type || "" + color: Looks.colors.accentUnfocused + font.pixelSize: Looks.font.pixelSize.normal + } + } + Rectangle { + id: resultSeparator + implicitHeight: 2 + Layout.topMargin: 16 + Layout.fillWidth: true + color: Looks.colors.bg2Hover + } + WListView { + id: actionsColumn + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + spacing: 2 + model: { + const isAppEntry = resultPreview.entry.type === Translation.tr("App"); + const appId = isAppEntry ? resultPreview.entry.id : ""; + const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false; + var result = [ + searchResultComp.createObject(null, { + name: resultPreview.entry.verb, + iconName: isAppEntry ? "open_in_new" : "keyboard_return", + iconType: LauncherSearchResult.IconType.Material, + execute: () => { + resultPreview.entry.execute(); + } + }), + ...(isAppEntry ? [ + searchResultComp.createObject(null, { + name: pinned ? Translation.tr("Unpin from taskbar") : Translation.tr("Pin to taskbar"), + iconName: pinned ? "keep_off" : "keep", + iconType: LauncherSearchResult.IconType.Material, + execute: () => { + TaskbarApps.togglePin(appId); + } + }) + ] : []) + ]; + result = result.concat(resultPreview.entry.actions); + return result; + } + delegate: WButton { + id: actionButton + required property var modelData + width: ListView.view?.width + icon.name: modelData.iconName + text: modelData.name + onClicked: modelData.execute(); + + contentItem: RowLayout { + spacing: 11 + SearchEntryIcon { + entry: actionButton.modelData + iconSize: 16 + } + WText { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + text: actionButton.text + } + } + } + } + } + } + + Component { + id: searchResultComp + LauncherSearchResult {} } } diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml index 09de017ee..2f76ca0ca 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml @@ -16,10 +16,72 @@ WBarAttachedPanelContent { property bool searching: false property string searchText: LauncherSearch.query + StartMenuContext { + id: context + } + + Keys.onPressed: event => { + // Prevent Esc and Backspace from registering + if (event.key === Qt.Key_Escape) + return; + + // Handle Backspace: focus and delete character if not focused + if (event.key === Qt.Key_Backspace) { + searchBar.forceFocus(); + if (event.modifiers & Qt.ControlModifier) { + // Delete word before cursor + let text = searchBar.text; + let pos = searchBar.searchInput.cursorPosition; + if (pos > 0) { + // Find the start of the previous word + let left = text.slice(0, pos); + let match = left.match(/(\s*\S+)\s*$/); + let deleteLen = match ? match[0].length : 1; + searchBar.text = text.slice(0, pos - deleteLen) + text.slice(pos); + searchBar.searchInput.cursorPosition = pos - deleteLen; + } + } else { + // Delete character before cursor if any + if (searchBar.searchInput.cursorPosition > 0) { + searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.text.slice(searchBar.searchInput.cursorPosition); + searchBar.searchInput.cursorPosition -= 1; + } + } + // Always move cursor to end after programmatic edit + searchBar.searchInput.cursorPosition = searchBar.text.length; + event.accepted = true; + // If already focused, let TextField handle it + return; + } + + // Only handle visible printable characters (ignore control chars, arrows, etc.) + if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc. + { + if (!searchBar.searchInput.activeFocus) { + searchBar.forceFocus(); + // Insert the character at the cursor position + searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.text.slice(searchBar.searchInput.cursorPosition); + searchBar.searchInput.cursorPosition += 1; + event.accepted = true; + context.setCurrentIndex(0); + } + } + + // Arrow keys for item navigation + if (event.key === Qt.Key_Down) { + let maxIndex = Math.max(0, LauncherSearch.results.length - 1); + context.setCurrentIndex(Math.min(context.currentIndex + 1, maxIndex)); + event.accepted = true; + } else if (event.key === Qt.Key_Up) { + context.setCurrentIndex(Math.max(context.currentIndex - 1, 0)); + event.accepted = true; + } + } + contentItem: WPane { contentItem: WPanelPageColumn { SearchBar { - focus: true + id: searchBar Layout.fillWidth: true implicitWidth: 832 // TODO: Make sizes naturally inferred horizontalPadding: root.searching ? 24 : 32 @@ -27,10 +89,14 @@ WBarAttachedPanelContent { Synchronizer on searching { property alias target: root.searching } + focus: true text: root.searchText onTextChanged: { LauncherSearch.query = text; } + onAccepted: { + context.accepted(); + } } Item { implicitHeight: root.searching ? 736 : 736 // TODO: Make sizes naturally inferred @@ -46,7 +112,9 @@ WBarAttachedPanelContent { Component { id: searchPageComp - SearchPageContent {} + SearchPageContent { + context: context + } } Component { diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml new file mode 100644 index 000000000..ff2289da0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml @@ -0,0 +1,64 @@ +import QtQuick +import Quickshell +import Quickshell.Io +import qs +import qs.modules.common +import qs.services + +Scope { + id: root + + signal accepted + + property int currentIndex: 0 + function setCurrentIndex(index) { + if (index == currentIndex) + return; + currentIndex = index; + } + + function selectCategory(category) { + for (let i = 0; i < root.categories.length; i++) { + const thisCategoryName = root.categories[i].name; + if (thisCategoryName.startsWith(category) || category.startsWith(thisCategoryName)) { + LauncherSearch.ensurePrefix(root.categories[i].prefix); + return; + } + } + } + property list categories: [ + { + name: Translation.tr("All"), + prefix: "" + }, + { + name: Translation.tr("Apps"), + prefix: Config.options.search.prefix.app + }, + { + name: Translation.tr("Actions"), + prefix: Config.options.search.prefix.action + }, + { + name: Translation.tr("Clipboard"), + prefix: Config.options.search.prefix.clipboard + }, + { + name: Translation.tr("Emojis"), + prefix: Config.options.search.prefix.emojis + }, + { + name: Translation.tr("Math"), + prefix: Config.options.search.prefix.math + }, + { + name: Translation.tr("Commands"), + prefix: Config.options.search.prefix.shellCommand + }, + { + name: Translation.tr("Web"), + prefix: Config.options.search.prefix.webSearch + }, + ] + +} diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml index 10448b5cd..ce7475815 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/TagStrip.qml @@ -10,6 +10,9 @@ import qs.modules.common.functions import qs.modules.waffle.looks RowLayout { + id: root + property StartMenuContext context + WPanelIconButton { implicitWidth: 36 implicitHeight: 36 @@ -23,40 +26,7 @@ RowLayout { Layout.fillHeight: true orientation: Qt.Horizontal spacing: 4 - model: [ - { - name: Translation.tr("All"), - prefix: "" - }, - { - name: Translation.tr("Apps"), - prefix: Config.options.search.prefix.app - }, - { - name: Translation.tr("Actions"), - prefix: Config.options.search.prefix.action - }, - { - name: Translation.tr("Clipboard"), - prefix: Config.options.search.prefix.clipboard - }, - { - name: Translation.tr("Emojis"), - prefix: Config.options.search.prefix.emojis - }, - { - name: Translation.tr("Math"), - prefix: Config.options.search.prefix.math - }, - { - name: Translation.tr("Commands"), - prefix: Config.options.search.prefix.shellCommand - }, - { - name: Translation.tr("Web"), - prefix: Config.options.search.prefix.webSearch - }, - ] + model: root.context.categories delegate: WBorderedButton { id: tagButton required property var modelData diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml index 1aca104b0..4cd28fa9a 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/WSearchResultButton.qml @@ -11,7 +11,7 @@ import qs.modules.waffle.looks WChoiceButton { id: root - + required property LauncherSearchResult entry property bool firstEntry: false @@ -21,45 +21,28 @@ WChoiceButton { implicitHeight: contentLayout.implicitHeight + topPadding + bottomPadding onClicked: { - GlobalStates.searchOpen = false - root.entry.execute() + execute(); } - + + function execute() { + GlobalStates.searchOpen = false; + root.entry.execute(); + } + contentItem: RowLayout { id: contentLayout spacing: 8 - - EntryIcon {} + + SearchEntryIcon { + entry: root.entry + iconSize: 24 + } EntryNameColumn { Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter } } - component EntryIcon: Item { - implicitWidth: 24 - implicitHeight: 24 - Loader { - anchors.centerIn: parent - active: root.entry.iconType === LauncherSearchResult.IconType.System - sourceComponent: WAppIcon { - implicitSize: 24 - tryCustomIcon: false - iconName: root.entry.iconName - } - } - Loader { - anchors.centerIn: parent - active: root.entry.iconType === LauncherSearchResult.IconType.Text - sourceComponent: WText { - text: root.entry.iconName - font.pixelSize: 24 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - component EntryNameColumn: ColumnLayout { spacing: 4 @@ -78,4 +61,11 @@ WChoiceButton { color: Looks.colors.accentUnfocused } } + + MouseArea { + anchors.fill: parent + // hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: Qt.PointingHandCursor + } } diff --git a/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml b/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml index 9b59fb0c8..15ffc6f43 100644 --- a/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml +++ b/dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml @@ -15,8 +15,10 @@ Scope { target: GlobalStates function onSearchOpenChanged() { - if (GlobalStates.searchOpen) + if (GlobalStates.searchOpen) { + LauncherSearch.query = ""; panelLoader.active = true; + } } } @@ -62,6 +64,7 @@ Scope { onClosed: { GlobalStates.searchOpen = false; panelLoader.active = false; + LauncherSearch.query = ""; } } } diff --git a/dots/.config/quickshell/ii/services/LauncherSearch.qml b/dots/.config/quickshell/ii/services/LauncherSearch.qml index 89d9e0056..49de2158e 100644 --- a/dots/.config/quickshell/ii/services/LauncherSearch.qml +++ b/dots/.config/quickshell/ii/services/LauncherSearch.qml @@ -198,10 +198,11 @@ Singleton { const appResultObjects = AppSearch.fuzzyQuery(StringUtils.cleanPrefix(root.query, Config.options.search.prefix.app)).map(entry => { return resultComp.createObject(null, { type: Translation.tr("App"), + id: entry.id, name: entry.name, iconName: entry.icon, iconType: LauncherSearchResult.IconType.System, - verb: Translation.tr("Launch"), + verb: Translation.tr("Open"), execute: () => { if (!entry.runInTerminal) entry.execute(); @@ -233,7 +234,7 @@ Singleton { const commandResultObject = resultComp.createObject(null, { name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.shellCommand).replace("file://", ""), verb: Translation.tr("Run"), - type: Translation.tr("Run command"), + type: Translation.tr("Command"), fontType: LauncherSearchResult.FontType.Monospace, iconName: 'terminal', iconType: LauncherSearchResult.IconType.Material, @@ -249,7 +250,7 @@ Singleton { const webSearchResultObject = resultComp.createObject(null, { name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch), verb: Translation.tr("Search"), - type: Translation.tr("Search the web"), + type: Translation.tr("Web search"), iconName: 'travel_explore', iconType: LauncherSearchResult.IconType.Material, execute: () => {