From 5faa1a993a7be094151db8c27f5f874cee6acc92 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 5 Dec 2025 10:04:19 -0500 Subject: [PATCH] launcher: reemove background from list and add a bottom fade --- .../Modals/Spotlight/FileSearchResults.qml | 95 ++++++++++++------- .../Modals/Spotlight/SpotlightResults.qml | 49 ++++++++-- .../Modules/AppDrawer/AppDrawerPopout.qml | 44 +++++++-- .../Widgets/AppLauncherListDelegate.qml | 22 ++--- 4 files changed, 150 insertions(+), 60 deletions(-) diff --git a/quickshell/Modals/Spotlight/FileSearchResults.qml b/quickshell/Modals/Spotlight/FileSearchResults.qml index 2b0895ca..340dc525 100644 --- a/quickshell/Modals/Spotlight/FileSearchResults.qml +++ b/quickshell/Modals/Spotlight/FileSearchResults.qml @@ -10,12 +10,31 @@ Rectangle { property var fileSearchController: null function resetScroll() { - filesList.contentY = 0 + filesList.contentY = 0; } color: "transparent" clip: true + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 32 + z: 100 + visible: filesList.contentHeight > filesList.height && (filesList.currentIndex < filesList.count - 1 || filesList.contentY < filesList.contentHeight - filesList.height - 1) + gradient: Gradient { + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: 1.0 + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + } + } + } + DankListView { id: filesList @@ -30,18 +49,22 @@ Rectangle { function ensureVisible(index) { if (index < 0 || index >= count) - return - - const itemY = index * (itemHeight + itemSpacing) - const itemBottom = itemY + itemHeight + return; + const itemY = index * (itemHeight + itemSpacing); + const itemBottom = itemY + itemHeight; + const fadeHeight = 32; + const isLastItem = index === count - 1; if (itemY < contentY) - contentY = itemY - else if (itemBottom > contentY + height) - contentY = itemBottom - height + contentY = itemY; + else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight)) + contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height); } anchors.fill: parent - anchors.margins: Theme.spacingS + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: 1 model: fileSearchController ? fileSearchController.model : null currentIndex: fileSearchController ? fileSearchController.selectedIndex : -1 clip: true @@ -53,26 +76,26 @@ Rectangle { onCurrentIndexChanged: { if (keyboardNavigationActive) - ensureVisible(currentIndex) + ensureVisible(currentIndex); } onItemClicked: function (index) { if (fileSearchController) { - const item = fileSearchController.model.get(index) - fileSearchController.openFile(item.filePath) + const item = fileSearchController.model.get(index); + fileSearchController.openFile(item.filePath); } } onItemRightClicked: function (index) { if (fileSearchController) { - const item = fileSearchController.model.get(index) - fileSearchController.openFolder(item.filePath) + const item = fileSearchController.model.get(index); + fileSearchController.openFolder(item.filePath); } } onKeyboardNavigationReset: { if (fileSearchController) - fileSearchController.keyboardNavigationActive = false + fileSearchController.keyboardNavigationActive = false; } delegate: Rectangle { @@ -86,7 +109,7 @@ Rectangle { width: ListView.view.width height: filesList.itemHeight radius: Theme.cornerRadius - color: ListView.isCurrentItem ? Theme.primaryPressed : fileMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + color: ListView.isCurrentItem ? Theme.widgetBaseHoverColor : fileMouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" Row { anchors.fill: parent @@ -109,16 +132,16 @@ Rectangle { id: nerdIcon anchors.centerIn: parent name: { - const lowerName = fileName.toLowerCase() + const lowerName = fileName.toLowerCase(); if (lowerName.startsWith("dockerfile")) - return "docker" + return "docker"; if (lowerName.startsWith("makefile")) - return "makefile" + return "makefile"; if (lowerName.startsWith("license")) - return "license" + return "license"; if (lowerName.startsWith("readme")) - return "readme" - return fileExtension.toLowerCase() + return "readme"; + return fileExtension.toLowerCase(); } size: Theme.fontSizeXLarge color: Theme.surfaceText @@ -196,18 +219,18 @@ Rectangle { z: 10 onEntered: { if (filesList.hoverUpdatesSelection && !filesList.keyboardNavigationActive) - filesList.currentIndex = index + filesList.currentIndex = index; } onPositionChanged: { - filesList.keyboardNavigationReset() + filesList.keyboardNavigationReset(); } onClicked: mouse => { - if (mouse.button === Qt.LeftButton) { - filesList.itemClicked(index) - } else if (mouse.button === Qt.RightButton) { - filesList.itemRightClicked(index) - } - } + if (mouse.button === Qt.LeftButton) { + filesList.itemClicked(index); + } else if (mouse.button === Qt.RightButton) { + filesList.itemRightClicked(index); + } + } } } } @@ -219,21 +242,21 @@ Rectangle { StyledText { property string displayText: { if (!fileSearchController) { - return "" + return ""; } if (!DSearchService.dsearchAvailable) { - return I18n.tr("DankSearch not available") + return I18n.tr("DankSearch not available"); } if (fileSearchController.isSearching) { - return I18n.tr("Searching...") + return I18n.tr("Searching..."); } if (fileSearchController.searchQuery.length === 0) { - return I18n.tr("Enter a search query") + return I18n.tr("Enter a search query"); } if (!fileSearchController.model || fileSearchController.model.count === 0) { - return I18n.tr("No files found") + return I18n.tr("No files found"); } - return "" + return ""; } text: displayText diff --git a/quickshell/Modals/Spotlight/SpotlightResults.qml b/quickshell/Modals/Spotlight/SpotlightResults.qml index 12464134..165b8a49 100644 --- a/quickshell/Modals/Spotlight/SpotlightResults.qml +++ b/quickshell/Modals/Spotlight/SpotlightResults.qml @@ -51,6 +51,33 @@ Rectangle { color: "transparent" clip: true + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 32 + z: 100 + visible: { + if (!appLauncher) + return false; + const view = appLauncher.viewMode === "list" ? resultsList : (gridLoader.item || resultsList); + const isLastItem = appLauncher.viewMode === "list" ? view.currentIndex >= view.count - 1 : (gridLoader.item ? Math.floor(view.currentIndex / view.actualColumns) >= Math.floor((view.count - 1) / view.actualColumns) : false); + const hasOverflow = view.contentHeight > view.height; + const atBottom = view.contentY >= view.contentHeight - view.height - 1; + return hasOverflow && (!isLastItem || !atBottom); + } + gradient: Gradient { + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: 1.0 + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + } + } + } + DankListView { id: resultsList @@ -70,14 +97,19 @@ Rectangle { return; const itemY = index * (itemHeight + itemSpacing); const itemBottom = itemY + itemHeight; + const fadeHeight = 32; + const isLastItem = index === count - 1; if (itemY < contentY) contentY = itemY; - else if (itemBottom > contentY + height) - contentY = itemBottom - height; + else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight)) + contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height); } anchors.fill: parent - anchors.margins: Theme.spacingS + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: 1 visible: appLauncher && appLauncher.viewMode === "list" model: appLauncher ? appLauncher.model : null currentIndex: appLauncher ? appLauncher.selectedIndex : -1 @@ -127,7 +159,10 @@ Rectangle { property real _lastWidth: 0 anchors.fill: parent - anchors.margins: Theme.spacingS + anchors.leftMargin: Theme.spacingS + anchors.rightMargin: Theme.spacingS + anchors.topMargin: Theme.spacingS + anchors.bottomMargin: 1 visible: appLauncher && appLauncher.viewMode === "grid" active: appLauncher && appLauncher.viewMode === "grid" asynchronous: false @@ -177,10 +212,12 @@ Rectangle { return; const itemY = Math.floor(index / actualColumns) * cellHeight; const itemBottom = itemY + cellHeight; + const fadeHeight = 32; + const isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns); if (itemY < contentY) contentY = itemY; - else if (itemBottom > contentY + height) - contentY = itemBottom - height; + else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight)) + contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height); } anchors.fill: parent diff --git a/quickshell/Modules/AppDrawer/AppDrawerPopout.qml b/quickshell/Modules/AppDrawer/AppDrawerPopout.qml index 9fe405a5..54a7cc2d 100644 --- a/quickshell/Modules/AppDrawer/AppDrawerPopout.qml +++ b/quickshell/Modules/AppDrawer/AppDrawerPopout.qml @@ -406,6 +406,34 @@ DankPopout { } radius: Theme.cornerRadius color: "transparent" + clip: true + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 32 + z: 100 + visible: { + if (appDrawerPopout.searchMode !== "apps") + return false; + const view = appLauncher.viewMode === "list" ? appList : appGrid; + const isLastItem = view.currentIndex >= view.count - 1; + const hasOverflow = view.contentHeight > view.height; + const atBottom = view.contentY >= view.contentHeight - view.height - 1; + return hasOverflow && (!isLastItem || !atBottom); + } + gradient: Gradient { + GradientStop { + position: 0.0 + color: "transparent" + } + GradientStop { + position: 1.0 + color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) + } + } + } DankListView { id: appList @@ -426,14 +454,16 @@ DankPopout { return; var itemY = index * (itemHeight + itemSpacing); var itemBottom = itemY + itemHeight; + var fadeHeight = 32; + var isLastItem = index === count - 1; if (itemY < contentY) contentY = itemY; - else if (itemBottom > contentY + height) - contentY = itemBottom - height; + else if (itemBottom > contentY + height - (isLastItem ? 0 : fadeHeight)) + contentY = Math.min(itemBottom - height + (isLastItem ? 0 : fadeHeight), contentHeight - height); } anchors.fill: parent - anchors.bottomMargin: Theme.spacingS + anchors.bottomMargin: 1 visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "list" model: appLauncher.model currentIndex: appLauncher.selectedIndex @@ -511,14 +541,16 @@ DankPopout { return; var itemY = Math.floor(index / actualColumns) * cellHeight; var itemBottom = itemY + cellHeight; + var fadeHeight = 32; + var isLastRow = Math.floor(index / actualColumns) >= Math.floor((count - 1) / actualColumns); if (itemY < contentY) contentY = itemY; - else if (itemBottom > contentY + height) - contentY = itemBottom - height; + else if (itemBottom > contentY + height - (isLastRow ? 0 : fadeHeight)) + contentY = Math.min(itemBottom - height + (isLastRow ? 0 : fadeHeight), contentHeight - height); } anchors.fill: parent - anchors.bottomMargin: Theme.spacingS + anchors.bottomMargin: 1 visible: appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" model: appLauncher.model clip: true diff --git a/quickshell/Widgets/AppLauncherListDelegate.qml b/quickshell/Widgets/AppLauncherListDelegate.qml index 6e5b63a6..766dc6c4 100644 --- a/quickshell/Widgets/AppLauncherListDelegate.qml +++ b/quickshell/Widgets/AppLauncherListDelegate.qml @@ -1,6 +1,4 @@ import QtQuick -import QtQuick.Controls -import Quickshell import qs.Common import qs.Widgets @@ -29,12 +27,12 @@ Rectangle { signal itemClicked(int index, var modelData) signal itemRightClicked(int index, var modelData, real mouseX, real mouseY) - signal keyboardNavigationReset() + signal keyboardNavigationReset width: listView.width height: itemHeight radius: Theme.cornerRadius - color: isCurrentItem ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : mouseArea.containsMouse ? Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + color: isCurrentItem ? Theme.widgetBaseHoverColor : mouseArea.containsMouse ? Theme.widgetBaseHoverColor : "transparent" Row { anchors.fill: parent @@ -97,27 +95,27 @@ Rectangle { z: 10 onEntered: { if (root.hoverUpdatesSelection && !root.keyboardNavigationActive) - root.listView.currentIndex = root.index + root.listView.currentIndex = root.index; } onPositionChanged: { - root.keyboardNavigationReset() + root.keyboardNavigationReset(); } onClicked: mouse => { if (mouse.button === Qt.LeftButton) { - root.itemClicked(root.index, root.model) + root.itemClicked(root.index, root.model); } } onPressAndHold: mouse => { if (!root.isPlugin) { - const globalPos = mapToItem(null, mouse.x, mouse.y) - root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y) + const globalPos = mapToItem(null, mouse.x, mouse.y); + root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y); } } onPressed: mouse => { if (mouse.button === Qt.RightButton && !root.isPlugin) { - const globalPos = mapToItem(null, mouse.x, mouse.y) - root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y) - mouse.accepted = true + const globalPos = mapToItem(null, mouse.x, mouse.y); + root.itemRightClicked(root.index, root.model, globalPos.x, globalPos.y); + mouse.accepted = true; } } }