diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml
index b38a8d1e..a9f2a384 100644
--- a/quickshell/Modals/DankLauncherV2/Controller.qml
+++ b/quickshell/Modals/DankLauncherV2/Controller.qml
@@ -26,6 +26,8 @@ Item {
property string activePluginId: ""
property var collapsedSections: ({})
property bool keyboardNavigationActive: false
+ property var _modeSectionsCache: ({})
+ property bool _queryDrivenSearch: false
property var sectionViewModes: ({})
property var pluginViewPreferences: ({})
property int gridColumns: SettingsData.appLauncherGridColumns
@@ -42,6 +44,14 @@ Item {
target: SettingsData
function onSortAppsAlphabeticallyChanged() {
AppSearchService.invalidateLauncherCache();
+ _clearModeCache();
+ }
+ }
+
+ Connections {
+ target: AppSearchService
+ function onCacheInvalidated() {
+ _clearModeCache();
}
}
@@ -266,6 +276,7 @@ Item {
function setSearchQuery(query) {
_searchVersion++;
+ _queryDrivenSearch = true;
searchQuery = query;
searchDebounce.restart();
@@ -324,6 +335,8 @@ Item {
activePluginCategory = "";
pluginFilter = "";
collapsedSections = {};
+ _clearModeCache();
+ _queryDrivenSearch = false;
}
function loadPluginCategories(pluginId) {
@@ -369,7 +382,11 @@ Item {
return false;
}
- function preserveSelectionAfterUpdate() {
+ function preserveSelectionAfterUpdate(forceFirst) {
+ if (forceFirst)
+ return function () {
+ return getFirstItemIndex();
+ };
var previousSelectedId = selectedItem?.id || "";
return function (newFlatModel) {
if (!previousSelectedId)
@@ -385,7 +402,9 @@ Item {
function performSearch() {
var currentVersion = _searchVersion;
isSearching = true;
- var restoreSelection = preserveSelectionAfterUpdate();
+ var shouldResetSelection = _queryDrivenSearch;
+ _queryDrivenSearch = false;
+ var restoreSelection = preserveSelectionAfterUpdate(shouldResetSelection);
var cachedSections = AppSearchService.getCachedDefaultSections();
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
@@ -394,15 +413,22 @@ Item {
activePluginCategories = [];
activePluginCategory = "";
clearActivePluginViewPreference();
- sections = cachedSections.map(function (s) {
- var copy = Object.assign({}, s, {
- items: s.items ? s.items.slice() : []
+ var modeCache = _getCachedModeData("all");
+ if (modeCache) {
+ sections = modeCache.sections;
+ flatModel = modeCache.flatModel;
+ } else {
+ sections = cachedSections.map(function (s) {
+ var copy = Object.assign({}, s, {
+ items: s.items ? s.items.slice() : []
+ });
+ if (collapsedSections[s.id] !== undefined)
+ copy.collapsed = collapsedSections[s.id];
+ return copy;
});
- if (collapsedSections[s.id] !== undefined)
- copy.collapsed = collapsedSections[s.id];
- return copy;
- });
- flatModel = Scorer.flattenSections(sections);
+ flatModel = Scorer.flattenSections(sections);
+ _setCachedModeData("all", sections, flatModel);
+ }
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
@@ -475,18 +501,25 @@ Item {
if (searchMode === "apps") {
var cachedSections = AppSearchService.getCachedDefaultSections();
if (cachedSections && !searchQuery) {
- var appSectionIds = ["favorites", "apps"];
- sections = cachedSections.filter(function (s) {
- return appSectionIds.indexOf(s.id) !== -1;
- }).map(function (s) {
- var copy = Object.assign({}, s, {
- items: s.items ? s.items.slice() : []
+ var modeCache = _getCachedModeData("apps");
+ if (modeCache) {
+ sections = modeCache.sections;
+ flatModel = modeCache.flatModel;
+ } else {
+ var appSectionIds = ["favorites", "apps"];
+ sections = cachedSections.filter(function (s) {
+ return appSectionIds.indexOf(s.id) !== -1;
+ }).map(function (s) {
+ var copy = Object.assign({}, s, {
+ items: s.items ? s.items.slice() : []
+ });
+ if (collapsedSections[s.id] !== undefined)
+ copy.collapsed = collapsedSections[s.id];
+ return copy;
});
- if (collapsedSections[s.id] !== undefined)
- copy.collapsed = collapsedSections[s.id];
- return copy;
- });
- flatModel = Scorer.flattenSections(sections);
+ flatModel = Scorer.flattenSections(sections);
+ _setCachedModeData("apps", sections, flatModel);
+ }
selectedFlatIndex = restoreSelection(flatModel);
updateSelectedItem();
isSearching = false;
@@ -1055,6 +1088,23 @@ Item {
return Nav.getFirstItemIndex(flatModel);
}
+ function _getCachedModeData(mode) {
+ return _modeSectionsCache[mode] || null;
+ }
+
+ function _setCachedModeData(mode, sectionsData, flatModelData) {
+ var cache = Object.assign({}, _modeSectionsCache);
+ cache[mode] = {
+ sections: sectionsData,
+ flatModel: flatModelData
+ };
+ _modeSectionsCache = cache;
+ }
+
+ function _clearModeCache() {
+ _modeSectionsCache = {};
+ }
+
function updateSelectedItem() {
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
var entry = flatModel[selectedFlatIndex];
@@ -1158,6 +1208,7 @@ Item {
}
function toggleSection(sectionId) {
+ _clearModeCache();
var newCollapsed = Object.assign({}, collapsedSections);
var currentState = newCollapsed[sectionId];
diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
index 02d4a1e9..c7570312 100644
--- a/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
+++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2Modal.qml
@@ -90,6 +90,8 @@ Item {
spotlightContent.controller.activePluginName = "";
spotlightContent.controller.pluginFilter = "";
spotlightContent.controller.collapsedSections = {};
+ spotlightContent.controller.selectedFlatIndex = 0;
+ spotlightContent.controller.selectedItem = null;
if (query) {
spotlightContent.controller.setSearchQuery(query);
} else {
diff --git a/quickshell/Modals/DankLauncherV2/GridItem.qml b/quickshell/Modals/DankLauncherV2/GridItem.qml
index 1da51d72..63bb645b 100644
--- a/quickshell/Modals/DankLauncherV2/GridItem.qml
+++ b/quickshell/Modals/DankLauncherV2/GridItem.qml
@@ -35,6 +35,21 @@ Rectangle {
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
+ function highlightText(text, query, baseColor) {
+ if (!text || !query || query.length === 0)
+ return text;
+ var lowerText = text.toLowerCase();
+ var lowerQuery = query.toLowerCase();
+ var idx = lowerText.indexOf(lowerQuery);
+ if (idx === -1)
+ return text;
+ var before = text.substring(0, idx);
+ var match = text.substring(idx, idx + query.length);
+ var after = text.substring(idx + query.length);
+ var highlightColor = Theme.primary;
+ return '' + before + '' + '' + match + '' + '' + after + '';
+ }
+
radius: Theme.cornerRadius
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
@@ -55,11 +70,20 @@ Rectangle {
materialIconSizeAdjustment: root.computedIconSize * 0.3
}
- StyledText {
+ Text {
width: parent.width
- text: root.item?.name ?? ""
+ text: {
+ var query = root.controller?.searchQuery ?? "";
+ var name = root.item?.name ?? "";
+ var baseColor = root.isSelected ? Theme.primary : Theme.surfaceText;
+ if (!query)
+ return name;
+ return root.highlightText(name, query, baseColor);
+ }
+ textFormat: root.controller?.searchQuery ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
+ font.family: Theme.fontFamily
color: root.isSelected ? Theme.primary : Theme.surfaceText
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
diff --git a/quickshell/Modals/DankLauncherV2/ResultItem.qml b/quickshell/Modals/DankLauncherV2/ResultItem.qml
index c8ebdb7f..fab140cb 100644
--- a/quickshell/Modals/DankLauncherV2/ResultItem.qml
+++ b/quickshell/Modals/DankLauncherV2/ResultItem.qml
@@ -33,6 +33,21 @@ Rectangle {
}
}
+ function highlightText(text, query, baseColor) {
+ if (!text || !query || query.length === 0)
+ return text;
+ var lowerText = text.toLowerCase();
+ var lowerQuery = query.toLowerCase();
+ var idx = lowerText.indexOf(lowerQuery);
+ if (idx === -1)
+ return text;
+ var before = text.substring(0, idx);
+ var match = text.substring(idx, idx + query.length);
+ var after = text.substring(idx + query.length);
+ var highlightColor = Theme.primary;
+ return '' + before + '' + '' + match + '' + '' + after + '';
+ }
+
width: parent?.width ?? 200
height: 52
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
@@ -82,23 +97,41 @@ Rectangle {
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
spacing: 2
- StyledText {
+ Text {
width: parent.width
- text: root.item?.name ?? ""
+ text: {
+ var query = root.controller?.searchQuery ?? "";
+ var name = root.item?.name ?? "";
+ if (!query)
+ return name;
+ return root.highlightText(name, query, Theme.surfaceText);
+ }
+ textFormat: root.controller?.searchQuery ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
+ font.family: Theme.fontFamily
color: Theme.surfaceText
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
}
- StyledText {
+ Text {
width: parent.width
- text: root.item?.subtitle ?? ""
+ text: {
+ var query = root.controller?.searchQuery ?? "";
+ var subtitle = root.item?.subtitle ?? "";
+ if (!subtitle)
+ return "";
+ if (!query)
+ return subtitle;
+ return root.highlightText(subtitle, query, Theme.surfaceVariantText);
+ }
+ textFormat: root.controller?.searchQuery ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
+ font.family: Theme.fontFamily
color: Theme.surfaceVariantText
elide: Text.ElideRight
- visible: text.length > 0
+ visible: (root.item?.subtitle ?? "").length > 0
horizontalAlignment: Text.AlignLeft
}
}
diff --git a/quickshell/Modals/DankLauncherV2/TileItem.qml b/quickshell/Modals/DankLauncherV2/TileItem.qml
index 6f370cfa..e6e6af19 100644
--- a/quickshell/Modals/DankLauncherV2/TileItem.qml
+++ b/quickshell/Modals/DankLauncherV2/TileItem.qml
@@ -23,6 +23,21 @@ Rectangle {
border.width: isSelected ? 2 : 0
border.color: Theme.primary
+ function highlightText(text, query, baseColor) {
+ if (!text || !query || query.length === 0)
+ return text;
+ var lowerText = text.toLowerCase();
+ var lowerQuery = query.toLowerCase();
+ var idx = lowerText.indexOf(lowerQuery);
+ if (idx === -1)
+ return text;
+ var before = text.substring(0, idx);
+ var match = text.substring(idx, idx + query.length);
+ var after = text.substring(idx + query.length);
+ var highlightColor = Theme.primary;
+ return '' + before + '' + '' + match + '' + '' + after + '';
+ }
+
readonly property string toplevelId: item?.data?.toplevelId ?? ""
readonly property var waylandToplevel: {
if (!toplevelId || !item?.pluginId)
@@ -108,12 +123,20 @@ Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, 0.85)
visible: root.item?.name?.length > 0
- StyledText {
+ Text {
id: labelText
anchors.fill: parent
anchors.margins: Theme.spacingXS
- text: root.item?.name ?? ""
+ text: {
+ var query = root.controller?.searchQuery ?? "";
+ var name = root.item?.name ?? "";
+ if (!query)
+ return name;
+ return root.highlightText(name, query, Theme.surfaceText);
+ }
+ textFormat: root.controller?.searchQuery ? Text.RichText : Text.PlainText
font.pixelSize: Theme.fontSizeSmall
+ font.family: Theme.fontFamily
color: Theme.surfaceText
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter