mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-13 01:02:18 -04:00
launcher v2: improve search result responsiveness, highlight matches
This commit is contained in:
@@ -26,6 +26,8 @@ Item {
|
|||||||
property string activePluginId: ""
|
property string activePluginId: ""
|
||||||
property var collapsedSections: ({})
|
property var collapsedSections: ({})
|
||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
|
property var _modeSectionsCache: ({})
|
||||||
|
property bool _queryDrivenSearch: false
|
||||||
property var sectionViewModes: ({})
|
property var sectionViewModes: ({})
|
||||||
property var pluginViewPreferences: ({})
|
property var pluginViewPreferences: ({})
|
||||||
property int gridColumns: SettingsData.appLauncherGridColumns
|
property int gridColumns: SettingsData.appLauncherGridColumns
|
||||||
@@ -42,6 +44,14 @@ Item {
|
|||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onSortAppsAlphabeticallyChanged() {
|
function onSortAppsAlphabeticallyChanged() {
|
||||||
AppSearchService.invalidateLauncherCache();
|
AppSearchService.invalidateLauncherCache();
|
||||||
|
_clearModeCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: AppSearchService
|
||||||
|
function onCacheInvalidated() {
|
||||||
|
_clearModeCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +276,7 @@ Item {
|
|||||||
|
|
||||||
function setSearchQuery(query) {
|
function setSearchQuery(query) {
|
||||||
_searchVersion++;
|
_searchVersion++;
|
||||||
|
_queryDrivenSearch = true;
|
||||||
searchQuery = query;
|
searchQuery = query;
|
||||||
searchDebounce.restart();
|
searchDebounce.restart();
|
||||||
|
|
||||||
@@ -324,6 +335,8 @@ Item {
|
|||||||
activePluginCategory = "";
|
activePluginCategory = "";
|
||||||
pluginFilter = "";
|
pluginFilter = "";
|
||||||
collapsedSections = {};
|
collapsedSections = {};
|
||||||
|
_clearModeCache();
|
||||||
|
_queryDrivenSearch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPluginCategories(pluginId) {
|
function loadPluginCategories(pluginId) {
|
||||||
@@ -369,7 +382,11 @@ Item {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function preserveSelectionAfterUpdate() {
|
function preserveSelectionAfterUpdate(forceFirst) {
|
||||||
|
if (forceFirst)
|
||||||
|
return function () {
|
||||||
|
return getFirstItemIndex();
|
||||||
|
};
|
||||||
var previousSelectedId = selectedItem?.id || "";
|
var previousSelectedId = selectedItem?.id || "";
|
||||||
return function (newFlatModel) {
|
return function (newFlatModel) {
|
||||||
if (!previousSelectedId)
|
if (!previousSelectedId)
|
||||||
@@ -385,7 +402,9 @@ Item {
|
|||||||
function performSearch() {
|
function performSearch() {
|
||||||
var currentVersion = _searchVersion;
|
var currentVersion = _searchVersion;
|
||||||
isSearching = true;
|
isSearching = true;
|
||||||
var restoreSelection = preserveSelectionAfterUpdate();
|
var shouldResetSelection = _queryDrivenSearch;
|
||||||
|
_queryDrivenSearch = false;
|
||||||
|
var restoreSelection = preserveSelectionAfterUpdate(shouldResetSelection);
|
||||||
|
|
||||||
var cachedSections = AppSearchService.getCachedDefaultSections();
|
var cachedSections = AppSearchService.getCachedDefaultSections();
|
||||||
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
|
if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) {
|
||||||
@@ -394,15 +413,22 @@ Item {
|
|||||||
activePluginCategories = [];
|
activePluginCategories = [];
|
||||||
activePluginCategory = "";
|
activePluginCategory = "";
|
||||||
clearActivePluginViewPreference();
|
clearActivePluginViewPreference();
|
||||||
sections = cachedSections.map(function (s) {
|
var modeCache = _getCachedModeData("all");
|
||||||
var copy = Object.assign({}, s, {
|
if (modeCache) {
|
||||||
items: s.items ? s.items.slice() : []
|
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)
|
flatModel = Scorer.flattenSections(sections);
|
||||||
copy.collapsed = collapsedSections[s.id];
|
_setCachedModeData("all", sections, flatModel);
|
||||||
return copy;
|
}
|
||||||
});
|
|
||||||
flatModel = Scorer.flattenSections(sections);
|
|
||||||
selectedFlatIndex = restoreSelection(flatModel);
|
selectedFlatIndex = restoreSelection(flatModel);
|
||||||
updateSelectedItem();
|
updateSelectedItem();
|
||||||
isSearching = false;
|
isSearching = false;
|
||||||
@@ -475,18 +501,25 @@ Item {
|
|||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
var cachedSections = AppSearchService.getCachedDefaultSections();
|
var cachedSections = AppSearchService.getCachedDefaultSections();
|
||||||
if (cachedSections && !searchQuery) {
|
if (cachedSections && !searchQuery) {
|
||||||
var appSectionIds = ["favorites", "apps"];
|
var modeCache = _getCachedModeData("apps");
|
||||||
sections = cachedSections.filter(function (s) {
|
if (modeCache) {
|
||||||
return appSectionIds.indexOf(s.id) !== -1;
|
sections = modeCache.sections;
|
||||||
}).map(function (s) {
|
flatModel = modeCache.flatModel;
|
||||||
var copy = Object.assign({}, s, {
|
} else {
|
||||||
items: s.items ? s.items.slice() : []
|
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)
|
flatModel = Scorer.flattenSections(sections);
|
||||||
copy.collapsed = collapsedSections[s.id];
|
_setCachedModeData("apps", sections, flatModel);
|
||||||
return copy;
|
}
|
||||||
});
|
|
||||||
flatModel = Scorer.flattenSections(sections);
|
|
||||||
selectedFlatIndex = restoreSelection(flatModel);
|
selectedFlatIndex = restoreSelection(flatModel);
|
||||||
updateSelectedItem();
|
updateSelectedItem();
|
||||||
isSearching = false;
|
isSearching = false;
|
||||||
@@ -1055,6 +1088,23 @@ Item {
|
|||||||
return Nav.getFirstItemIndex(flatModel);
|
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() {
|
function updateSelectedItem() {
|
||||||
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
|
if (selectedFlatIndex >= 0 && selectedFlatIndex < flatModel.length) {
|
||||||
var entry = flatModel[selectedFlatIndex];
|
var entry = flatModel[selectedFlatIndex];
|
||||||
@@ -1158,6 +1208,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleSection(sectionId) {
|
function toggleSection(sectionId) {
|
||||||
|
_clearModeCache();
|
||||||
var newCollapsed = Object.assign({}, collapsedSections);
|
var newCollapsed = Object.assign({}, collapsedSections);
|
||||||
var currentState = newCollapsed[sectionId];
|
var currentState = newCollapsed[sectionId];
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ Item {
|
|||||||
spotlightContent.controller.activePluginName = "";
|
spotlightContent.controller.activePluginName = "";
|
||||||
spotlightContent.controller.pluginFilter = "";
|
spotlightContent.controller.pluginFilter = "";
|
||||||
spotlightContent.controller.collapsedSections = {};
|
spotlightContent.controller.collapsedSections = {};
|
||||||
|
spotlightContent.controller.selectedFlatIndex = 0;
|
||||||
|
spotlightContent.controller.selectedItem = null;
|
||||||
if (query) {
|
if (query) {
|
||||||
spotlightContent.controller.setSearchQuery(query);
|
spotlightContent.controller.setSearchQuery(query);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -35,6 +35,21 @@ Rectangle {
|
|||||||
|
|
||||||
readonly property int computedIconSize: Math.min(48, Math.max(32, width * 0.45))
|
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 '<span style="color:' + baseColor + '">' + before + '</span>' + '<span style="color:' + highlightColor + '; font-weight:600">' + match + '</span>' + '<span style="color:' + baseColor + '">' + after + '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
||||||
|
|
||||||
@@ -55,11 +70,20 @@ Rectangle {
|
|||||||
materialIconSizeAdjustment: root.computedIconSize * 0.3
|
materialIconSizeAdjustment: root.computedIconSize * 0.3
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
Text {
|
||||||
width: parent.width
|
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.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
font.family: Theme.fontFamily
|
||||||
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
color: root.isSelected ? Theme.primary : Theme.surfaceText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|||||||
@@ -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 '<span style="color:' + baseColor + '">' + before + '</span>' + '<span style="color:' + highlightColor + '; font-weight:600">' + match + '</span>' + '<span style="color:' + baseColor + '">' + after + '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
width: parent?.width ?? 200
|
width: parent?.width ?? 200
|
||||||
height: 52
|
height: 52
|
||||||
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
color: isSelected ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : "transparent"
|
||||||
@@ -82,23 +97,41 @@ Rectangle {
|
|||||||
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
|
width: parent.width - 36 - Theme.spacingM * 3 - rightContent.width
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
StyledText {
|
Text {
|
||||||
width: parent.width
|
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.pixelSize: Theme.fontSizeMedium
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
|
font.family: Theme.fontFamily
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
Text {
|
||||||
width: parent.width
|
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.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: Theme.fontFamily
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
visible: text.length > 0
|
visible: (root.item?.subtitle ?? "").length > 0
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,21 @@ Rectangle {
|
|||||||
border.width: isSelected ? 2 : 0
|
border.width: isSelected ? 2 : 0
|
||||||
border.color: Theme.primary
|
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 '<span style="color:' + baseColor + '">' + before + '</span>' + '<span style="color:' + highlightColor + '; font-weight:600">' + match + '</span>' + '<span style="color:' + baseColor + '">' + after + '</span>';
|
||||||
|
}
|
||||||
|
|
||||||
readonly property string toplevelId: item?.data?.toplevelId ?? ""
|
readonly property string toplevelId: item?.data?.toplevelId ?? ""
|
||||||
readonly property var waylandToplevel: {
|
readonly property var waylandToplevel: {
|
||||||
if (!toplevelId || !item?.pluginId)
|
if (!toplevelId || !item?.pluginId)
|
||||||
@@ -108,12 +123,20 @@ Rectangle {
|
|||||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.85)
|
color: Theme.withAlpha(Theme.surfaceContainer, 0.85)
|
||||||
visible: root.item?.name?.length > 0
|
visible: root.item?.name?.length > 0
|
||||||
|
|
||||||
StyledText {
|
Text {
|
||||||
id: labelText
|
id: labelText
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingXS
|
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.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.family: Theme.fontFamily
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|||||||
Reference in New Issue
Block a user