From 39a43f4de5af7e2b73a93a6749bb22fdfc0b3174 Mon Sep 17 00:00:00 2001 From: purian23 Date: Sun, 1 Mar 2026 18:34:13 -0500 Subject: [PATCH] feat: Reintroduce app filters in v2 launcher --- .../Modals/DankLauncherV2/Controller.qml | 55 +++++- .../Modals/DankLauncherV2/LauncherContent.qml | 7 +- .../Modals/DankLauncherV2/SectionHeader.qml | 171 ++++++++++++++++++ quickshell/Services/AppSearchService.qml | 6 + 4 files changed, 232 insertions(+), 7 deletions(-) diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml index 5d1acc73..f65ce900 100644 --- a/quickshell/Modals/DankLauncherV2/Controller.qml +++ b/quickshell/Modals/DankLauncherV2/Controller.qml @@ -51,6 +51,15 @@ Item { } } + onSearchModeChanged: { + if (searchMode === "apps") { + _loadAppCategories(); + } else { + appCategory = ""; + appCategories = []; + } + } + Connections { target: SettingsData function onSortAppsAlphabeticallyChanged() { @@ -65,8 +74,12 @@ Item { if (!active) return; _clearModeCache(); - if (!searchQuery && searchMode === "all") + if (searchMode === "apps") { + _loadAppCategories(); performSearch(); + } else if (!searchQuery && searchMode === "all") { + performSearch(); + } } } @@ -171,6 +184,8 @@ Item { property string activePluginName: "" property var activePluginCategories: [] property string activePluginCategory: "" + property string appCategory: "" + property var appCategories: [] function getSectionViewMode(sectionId) { if (sectionId === "browse_plugins") @@ -364,6 +379,8 @@ Item { activePluginName = ""; activePluginCategories = []; activePluginCategory = ""; + appCategory = ""; + appCategories = []; pluginFilter = ""; collapsedSections = {}; _clearModeCache(); @@ -408,6 +425,19 @@ Item { performSearch(); } + function setAppCategory(category) { + if (appCategory === category) + return; + appCategory = category; + _queryDrivenSearch = true; + _clearModeCache(); + performSearch(); + } + + function _loadAppCategories() { + appCategories = AppSearchService.getAllCategories(); + } + function setFileSearchType(type) { if (fileSearchType === type) return; @@ -592,8 +622,9 @@ Item { } if (searchMode === "apps") { + var isCategoryFiltered = appCategory && appCategory !== I18n.tr("All"); var cachedSections = AppSearchService.getCachedDefaultSections(); - if (cachedSections && !searchQuery) { + if (cachedSections && !searchQuery && !isCategoryFiltered) { var modeCache = _getCachedModeData("apps"); if (modeCache) { _applyHighlights(modeCache.sections, ""); @@ -623,9 +654,23 @@ Item { return; } - var apps = searchApps(searchQuery); - for (var i = 0; i < apps.length; i++) { - allItems.push(apps[i]); + if (isCategoryFiltered) { + var rawApps = AppSearchService.getAppsInCategory(appCategory); + for (var i = 0; i < rawApps.length; i++) { + allItems.push(getOrTransformApp(rawApps[i])); + } + // Also include core apps (DMS Settings etc.) that match this category + var allCoreApps = AppSearchService.getCoreApps(""); + for (var i = 0; i < allCoreApps.length; i++) { + var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]); + if (coreAppCats.indexOf(appCategory) !== -1) + allItems.push(transformCoreApp(allCoreApps[i])); + } + } else { + var apps = searchApps(searchQuery); + for (var i = 0; i < apps.length; i++) { + allItems.push(apps[i]); + } } var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem); diff --git a/quickshell/Modals/DankLauncherV2/LauncherContent.qml b/quickshell/Modals/DankLauncherV2/LauncherContent.qml index 271888ae..799c209c 100644 --- a/quickshell/Modals/DankLauncherV2/LauncherContent.qml +++ b/quickshell/Modals/DankLauncherV2/LauncherContent.qml @@ -496,8 +496,9 @@ FocusScope { Row { id: categoryRow width: parent.width - height: controller.activePluginCategories.length > 0 ? 36 : 0 - visible: controller.activePluginCategories.length > 0 + readonly property bool showPluginCategories: controller.activePluginCategories.length > 0 + height: showPluginCategories ? 36 : 0 + visible: showPluginCategories spacing: Theme.spacingS clip: true @@ -511,6 +512,7 @@ FocusScope { DankDropdown { id: categoryDropdown + visible: categoryRow.showPluginCategories width: Math.min(200, parent.width) compactMode: true dropdownWidth: 200 @@ -546,6 +548,7 @@ FocusScope { } } } + } Item { diff --git a/quickshell/Modals/DankLauncherV2/SectionHeader.qml b/quickshell/Modals/DankLauncherV2/SectionHeader.qml index a09af876..ee366c75 100644 --- a/quickshell/Modals/DankLauncherV2/SectionHeader.qml +++ b/quickshell/Modals/DankLauncherV2/SectionHeader.qml @@ -1,7 +1,9 @@ pragma ComponentBehavior: Bound import QtQuick +import QtQuick.Controls import qs.Common +import qs.Services import qs.Widgets Rectangle { @@ -35,21 +37,190 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter spacing: Theme.spacingS + // Whether the apps category picker should replace the plain title + readonly property bool hasAppCategories: root.section?.id === "apps" && (root.controller?.appCategories?.length ?? 0) > 0 + DankIcon { anchors.verticalCenter: parent.verticalCenter + // Hide section icon when the category chip already shows one + visible: !leftContent.hasAppCategories name: root.section?.icon ?? "folder" size: 16 color: Theme.surfaceVariantText } + // Plain title — hidden when the category chip is shown StyledText { anchors.verticalCenter: parent.verticalCenter + visible: !leftContent.hasAppCategories text: root.section?.title ?? "" font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium color: Theme.surfaceVariantText } + // Compact inline category chip — only visible on the apps section + Item { + id: categoryChip + visible: leftContent.hasAppCategories + anchors.verticalCenter: parent.verticalCenter + // Size to content with a fixed-min width so it doesn't jump around + width: chipRow.implicitWidth + Theme.spacingM * 2 + height: 24 + + readonly property string currentCategory: root.controller?.appCategory || (root.controller?.appCategories?.length > 0 ? root.controller.appCategories[0] : "") + readonly property var iconMap: { + const cats = root.controller?.appCategories ?? []; + const m = {}; + cats.forEach(c => { m[c] = AppSearchService.getCategoryIcon(c); }); + return m; + } + + Rectangle { + anchors.fill: parent + radius: Theme.cornerRadius + color: chipArea.containsMouse || categoryPopup.visible ? Theme.surfaceContainerHigh : "transparent" + border.color: categoryPopup.visible ? Theme.primary : Theme.outlineMedium + border.width: categoryPopup.visible ? 2 : 1 + } + + Row { + id: chipRow + anchors.centerIn: parent + spacing: Theme.spacingXS + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: categoryChip.iconMap[categoryChip.currentCategory] ?? "apps" + size: 14 + color: Theme.surfaceText + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: categoryChip.currentCategory + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + } + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: categoryPopup.visible ? "expand_less" : "expand_more" + size: 14 + color: Theme.surfaceVariantText + } + } + + MouseArea { + id: chipArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (categoryPopup.visible) { + categoryPopup.close(); + } else { + const pos = categoryChip.mapToItem(Overlay.overlay, 0, 0); + categoryPopup.x = pos.x; + categoryPopup.y = pos.y + categoryChip.height + 4; + categoryPopup.open(); + } + } + } + + Popup { + id: categoryPopup + parent: Overlay.overlay + width: Math.max(categoryChip.width, 180) + padding: 0 + modal: true + dim: false + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + background: Rectangle { color: "transparent" } + + contentItem: Rectangle { + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1) + border.color: Theme.primary + border.width: 2 + + ElevationShadow { + anchors.fill: parent + z: -1 + level: Theme.elevationLevel2 + fallbackOffset: 4 + targetRadius: parent.radius + targetColor: parent.color + borderColor: parent.border.color + borderWidth: parent.border.width + shadowEnabled: Theme.elevationEnabled && SettingsData.popoutElevationEnabled + } + + ListView { + id: categoryList + anchors.fill: parent + anchors.margins: Theme.spacingS + model: root.controller?.appCategories ?? [] + spacing: 2 + clip: true + interactive: contentHeight > height + implicitHeight: contentHeight + + delegate: Rectangle { + id: catDelegate + required property string modelData + required property int index + width: categoryList.width + height: 32 + radius: Theme.cornerRadius + readonly property bool isCurrent: categoryChip.currentCategory === modelData + color: isCurrent ? Theme.primaryHover : catArea.containsMouse ? Theme.primaryHoverLight : "transparent" + + Row { + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + spacing: Theme.spacingS + + DankIcon { + anchors.verticalCenter: parent.verticalCenter + name: categoryChip.iconMap[catDelegate.modelData] ?? "apps" + size: 16 + color: catDelegate.isCurrent ? Theme.primary : Theme.surfaceText + } + + StyledText { + anchors.verticalCenter: parent.verticalCenter + text: catDelegate.modelData + font.pixelSize: Theme.fontSizeMedium + color: catDelegate.isCurrent ? Theme.primary : Theme.surfaceText + font.weight: catDelegate.isCurrent ? Font.Medium : Font.Normal + } + } + + MouseArea { + id: catArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + if (root.controller) + root.controller.setAppCategory(catDelegate.modelData); + categoryPopup.close(); + } + } + } + } + } + + // Size to list content, cap at 10 visible items + height: Math.min((root.controller?.appCategories?.length ?? 0) * 34, 10 * 34) + Theme.spacingS * 2 + 4 + } + } + StyledText { anchors.verticalCenter: parent.verticalCenter text: root.section?.items?.length ?? 0 diff --git a/quickshell/Services/AppSearchService.qml b/quickshell/Services/AppSearchService.qml index c6b4913a..59bf0e9d 100644 --- a/quickshell/Services/AppSearchService.qml +++ b/quickshell/Services/AppSearchService.qml @@ -687,6 +687,12 @@ Singleton { appCategories.forEach(cat => categories.add(cat)); } + // Include categories from core apps (e.g. DMS Settings) + for (const app of coreApps) { + const appCategories = getCategoriesForApp(app); + appCategories.forEach(cat => categories.add(cat)); + } + const pluginCategories = getPluginCategories(); pluginCategories.forEach(cat => categories.add(cat));