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: () => {