From 01b1a276c586dd60af76d44152aec8b63be6b2df Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 23 Jan 2026 21:29:01 -0500 Subject: [PATCH] launcher v2: support ScreenCopy in tiles --- .../Modals/DankLauncherV2/Controller.qml | 35 ++++++++---- quickshell/Modals/DankLauncherV2/TileItem.qml | 53 ++++++++++++++++--- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/quickshell/Modals/DankLauncherV2/Controller.qml b/quickshell/Modals/DankLauncherV2/Controller.qml index 339249da..4fe5e9d8 100644 --- a/quickshell/Modals/DankLauncherV2/Controller.qml +++ b/quickshell/Modals/DankLauncherV2/Controller.qml @@ -147,6 +147,10 @@ Item { if (sectionDefinitions[i].id === sectionId) return sectionDefinitions[i].defaultViewMode || "list"; } + + if (pluginViewPreferences[sectionId]?.mode) + return pluginViewPreferences[sectionId].mode; + return "list"; } @@ -313,9 +317,23 @@ Item { return false; } + function preserveSelectionAfterUpdate() { + var previousSelectedId = selectedItem?.id || ""; + return function (newFlatModel) { + if (!previousSelectedId) + return getFirstItemIndex(); + for (var i = 0; i < newFlatModel.length; i++) { + if (!newFlatModel[i].isHeader && newFlatModel[i].item?.id === previousSelectedId) + return i; + } + return getFirstItemIndex(); + }; + } + function performSearch() { var currentVersion = _searchVersion; isSearching = true; + var restoreSelection = preserveSelectionAfterUpdate(); var cachedSections = AppSearchService.getCachedDefaultSections(); if (cachedSections && !searchQuery && searchMode === "all" && !pluginFilter) { @@ -331,7 +349,7 @@ Item { return copy; }); flatModel = Scorer.flattenSections(sections); - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; searchCompleted(); @@ -370,7 +388,7 @@ Item { } flatModel = Scorer.flattenSections(sections); - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; @@ -409,7 +427,7 @@ Item { return copy; }); flatModel = Scorer.flattenSections(sections); - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; searchCompleted(); @@ -434,7 +452,7 @@ Item { } flatModel = Scorer.flattenSections(sections); - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; @@ -489,7 +507,7 @@ Item { } flatModel = Scorer.flattenSections(sections); - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; @@ -573,7 +591,7 @@ Item { AppSearchService.setCachedDefaultSections(sections, flatModel); } - selectedFlatIndex = getFirstItemIndex(); + selectedFlatIndex = restoreSelection(flatModel); updateSelectedItem(); isSearching = false; @@ -1219,9 +1237,8 @@ Item { defaultViewMode: viewPref.mode || "list" }; - if (viewPref.enforced) { - setPluginViewPreference(section, viewPref.mode, true); - } + if (viewPref.mode) + setPluginViewPreference(section, viewPref.mode, viewPref.enforced); basePriority += 0.01; } diff --git a/quickshell/Modals/DankLauncherV2/TileItem.qml b/quickshell/Modals/DankLauncherV2/TileItem.qml index b0fea6db..6f370cfa 100644 --- a/quickshell/Modals/DankLauncherV2/TileItem.qml +++ b/quickshell/Modals/DankLauncherV2/TileItem.qml @@ -1,7 +1,9 @@ pragma ComponentBehavior: Bound import QtQuick +import Quickshell.Wayland import qs.Common +import qs.Services import qs.Widgets Rectangle { @@ -21,9 +23,22 @@ Rectangle { border.width: isSelected ? 2 : 0 border.color: Theme.primary + readonly property string toplevelId: item?.data?.toplevelId ?? "" + readonly property var waylandToplevel: { + if (!toplevelId || !item?.pluginId) + return null; + const pluginInstance = PluginService.pluginInstances[item.pluginId]; + if (!pluginInstance?.getToplevelById) + return null; + return pluginInstance.getToplevelById(toplevelId); + } + readonly property bool hasScreencopy: waylandToplevel !== null + readonly property string iconValue: { if (!item) return ""; + if (hasScreencopy) + return ""; var data = item.data; if (data?.imageUrl) return "image:" + data.imageUrl; @@ -63,12 +78,26 @@ Rectangle { color: Theme.surfaceContainerHigh clip: true + ScreencopyView { + id: screencopyView + anchors.fill: parent + captureSource: root.waylandToplevel + live: root.hasScreencopy + visible: root.hasScreencopy + + Rectangle { + anchors.fill: parent + color: root.isHovered ? Theme.withAlpha(Theme.surfaceVariant, 0.2) : "transparent" + } + } + AppIconRenderer { anchors.fill: parent iconValue: root.iconValue iconSize: Math.min(parent.width, parent.height) fallbackText: (root.item?.name?.length > 0) ? root.item.name.charAt(0).toUpperCase() : "?" materialIconSizeAdjustment: iconSize * 0.3 + visible: !root.hasScreencopy } Rectangle { @@ -110,16 +139,26 @@ Rectangle { } } - Image { + Rectangle { + id: attributionBadge anchors.top: parent.top anchors.left: parent.left anchors.margins: Theme.spacingXS - width: 40 - height: 16 - fillMode: Image.PreserveAspectFit - source: root.item?.data?.attribution || "" - visible: source !== "" - opacity: 0.9 + width: root.hasScreencopy ? 28 : 40 + height: root.hasScreencopy ? 28 : 16 + radius: root.hasScreencopy ? 14 : 4 + color: root.hasScreencopy ? Theme.surfaceContainer : "transparent" + visible: attributionImage.status === Image.Ready + opacity: 0.95 + + Image { + id: attributionImage + anchors.fill: parent + anchors.margins: root.hasScreencopy ? 4 : 0 + fillMode: Image.PreserveAspectFit + source: root.item?.data?.attribution || "" + mipmap: true + } } } }