From 61203dc5fd1e954314c1ba016d6bda269df764e8 Mon Sep 17 00:00:00 2001 From: LemmyCook Date: Fri, 19 Sep 2025 09:48:43 -0400 Subject: [PATCH] Wallpaper Selector: added screen tab for a better UX. --- .../WallpaperSelector/WallpaperSelector.qml | 525 +++++++++++------- 1 file changed, 310 insertions(+), 215 deletions(-) diff --git a/Modules/WallpaperSelector/WallpaperSelector.qml b/Modules/WallpaperSelector/WallpaperSelector.qml index cd855352..69c738af 100644 --- a/Modules/WallpaperSelector/WallpaperSelector.qml +++ b/Modules/WallpaperSelector/WallpaperSelector.qml @@ -21,65 +21,11 @@ NPanel { draggable: true panelContent: Rectangle { - // Local reactive state - property list wallpapersList: [] - property string currentWallpaper: "" + id: wallpaperPanel + + property int currentScreenIndex: 0 + property var currentScreen: Quickshell.screens[currentScreenIndex] property string filterText: "" - property list filteredWallpapers: [] - - Component.onCompleted: { - refreshWallpaperScreenData() - } - - Connections { - target: WallpaperService - function onWallpaperChanged(screenName, path) { - if (screen !== null && screenName === screen.name) { - currentWallpaper = WallpaperService.getWallpaper(screen.name) - } - } - function onWallpaperDirectoryChanged(screenName, directory) { - if (screen !== null && screenName === screen.name) { - refreshWallpaperScreenData() - } - } - function onWallpaperListChanged(screenName, count) { - if (screen !== null && screenName === screen.name) { - refreshWallpaperScreenData() - } - } - } - - function refreshWallpaperScreenData() { - if (screen === null) { - return - } - wallpapersList = WallpaperService.getWallpapersList(screen.name) - currentWallpaper = WallpaperService.getWallpaper(screen.name) - updateFiltered() - } - - function updateFiltered() { - if (!filterText || filterText.trim().length === 0) { - filteredWallpapers = wallpapersList - return - } - // Build objects with basename for ranking - const items = wallpapersList.map(function (p) { - return { - "path": p, - "name": p.split('/').pop() - } - }) - const results = FuzzySort.go(filterText.trim(), items, { - "key": 'name', - "limit": 200 - }) - // Map back to path list - filteredWallpapers = results.map(function (r) { - return r.obj.path - }) - } color: Color.transparent @@ -131,10 +77,88 @@ NPanel { description: "Apply selected wallpaper to all monitors at once." checked: Settings.data.wallpaper.setWallpaperOnAllMonitors onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked - visible: (wallpapersList.length > 0) Layout.fillWidth: true } + // Monitor tabs + TabBar { + id: screenTabBar + visible: !Settings.data.wallpaper.setWallpaperOnAllMonitors || Settings.data.wallpaper.enableMultiMonitorDirectories + Layout.fillWidth: true + currentIndex: currentScreenIndex + onCurrentIndexChanged: currentScreenIndex = currentIndex + spacing: Style.marginM * scaling + + background: Rectangle { + color: Color.transparent + } + + Repeater { + model: Quickshell.screens + delegate: TabButton { + text: modelData.name || `Screen ${index + 1}` + width: implicitWidth + Style.marginS * 2 * scaling + + background: Rectangle { + color: screenTabBar.currentIndex === index ? Color.mSecondary : Color.transparent + radius: Style.radiusS * scaling + border.width: screenTabBar.currentIndex === index ? 0 : Math.max(1, Style.borderS * scaling) + border.color: Color.mOutline + + Behavior on color { + ColorAnimation { + duration: Style.animationFast + } + } + } + + contentItem: Text { + text: parent.text + font.pointSize: Style.fontSizeL * scaling + font.weight: screenTabBar.currentIndex === index ? Style.fontWeightBold : Style.fontWeightRegular + font.family: Settings.data.ui.fontDefault + color: screenTabBar.currentIndex === index ? Color.mOnSecondary : Color.mOnSurfaceVariant + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + // Add hover effect + HoverHandler { + id: tabHover + } + + Rectangle { + anchors.fill: parent + color: Color.mOnSurface + opacity: tabHover.hovered && screenTabBar.currentIndex !== index ? 0.08 : 0 + radius: Style.radiusS * scaling + + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast + } + } + } + } + } + } + + // StackLayout for each screen's wallpaper content + StackLayout { + id: screenStack + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: currentScreenIndex + + Repeater { + id: screenRepeater + model: Quickshell.screens + delegate: WallpaperScreenView { + targetScreen: modelData + } + } + } + // Filter input RowLayout { Layout.fillWidth: true @@ -150,12 +174,19 @@ NPanel { NTextInput { id: searchInput placeholderText: "Type to filter wallpapers..." - text: filterText - onTextChanged: { - filterText = text - updateFiltered() - } Layout.fillWidth: true + + onTextChanged: { + wallpaperPanel.filterText = searchInput.text + // Trigger update on all screen views + for (var i = 0; i < screenRepeater.count; i++) { + let item = screenRepeater.itemAt(i) + if (item && item.updateFiltered) { + item.updateFiltered() + } + } + } + Component.onCompleted: { if (searchInput.inputItem && searchInput.inputItem.visible) { searchInput.inputItem.forceActiveFocus() @@ -163,186 +194,250 @@ NPanel { } } } + } + } - // Scroll container for wallpaper grid only - Flickable { - Layout.fillWidth: true - Layout.fillHeight: true - pressDelay: 200 + // Component for each screen's wallpaper view + component WallpaperScreenView: Item { + property var targetScreen - NScrollView { - id: scrollView - anchors.fill: parent - horizontalPolicy: ScrollBar.AlwaysOff - verticalPolicy: ScrollBar.AsNeeded - padding: Style.marginL * 0 * scaling - clip: true + // Local reactive state for this screen + property list wallpapersList: [] + property string currentWallpaper: "" + property list filteredWallpapers: [] - ColumnLayout { - width: scrollView.availableWidth - spacing: Style.marginM * scaling + // Expose updateFiltered as a proper function property + function updateFiltered() { + if (!wallpaperPanel.filterText || wallpaperPanel.filterText.trim().length === 0) { + filteredWallpapers = wallpapersList + return + } + // Build objects with basename for ranking + const items = wallpapersList.map(function (p) { + return { + "path": p, + "name": p.split('/').pop() + } + }) + const results = FuzzySort.go(wallpaperPanel.filterText.trim(), items, { + "key": 'name', + "limit": 200 + }) + // Map back to path list + filteredWallpapers = results.map(function (r) { + return r.obj.path + }) + } - // Grid container - Item { - visible: !WallpaperService.scanning - Layout.fillWidth: true - Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight + Component.onCompleted: { + refreshWallpaperScreenData() + } - GridView { - id: wallpaperGridView - anchors.fill: parent - model: filteredWallpapers - interactive: false + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (targetScreen !== null && screenName === targetScreen.name) { + currentWallpaper = WallpaperService.getWallpaper(targetScreen.name) + } + } + function onWallpaperDirectoryChanged(screenName, directory) { + if (targetScreen !== null && screenName === targetScreen.name) { + refreshWallpaperScreenData() + } + } + function onWallpaperListChanged(screenName, count) { + if (targetScreen !== null && screenName === targetScreen.name) { + refreshWallpaperScreenData() + } + } + } - property int columns: 4 - property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns) + function refreshWallpaperScreenData() { + if (targetScreen === null) { + return + } + wallpapersList = WallpaperService.getWallpapersList(targetScreen.name) + currentWallpaper = WallpaperService.getWallpaper(targetScreen.name) + updateFiltered() + } - cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) - cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling + // Scroll container for wallpaper grid only + Flickable { + anchors.fill: parent + pressDelay: 200 - leftMargin: Style.marginS * scaling - rightMargin: Style.marginS * scaling - topMargin: Style.marginS * scaling - bottomMargin: Style.marginS * scaling + NScrollView { + id: scrollView + anchors.fill: parent + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + padding: Style.marginL * 0 * scaling + clip: true - delegate: ColumnLayout { - id: wallpaperItem + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM * scaling - property string wallpaperPath: modelData - property bool isSelected: (wallpaperPath === currentWallpaper) - property string filename: wallpaperPath.split('/').pop() + // Grid container + Item { + visible: !WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: Math.ceil(filteredWallpapers.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight - width: wallpaperGridView.itemSize - spacing: Style.marginXS * scaling + GridView { + id: wallpaperGridView + anchors.fill: parent + model: filteredWallpapers + interactive: false + + property int columns: 4 + property int itemSize: Math.floor((width - leftMargin - rightMargin - (columns * Style.marginS * scaling)) / columns) + + cellWidth: Math.floor((width - leftMargin - rightMargin) / columns) + cellHeight: Math.floor(itemSize * 0.7) + Style.marginXS * scaling + Style.fontSizeXS * scaling + Style.marginM * scaling + + leftMargin: Style.marginS * scaling + rightMargin: Style.marginS * scaling + topMargin: Style.marginS * scaling + bottomMargin: Style.marginS * scaling + + delegate: ColumnLayout { + id: wallpaperItem + + property string wallpaperPath: modelData + property bool isSelected: (wallpaperPath === currentWallpaper) + property string filename: wallpaperPath.split('/').pop() + + width: wallpaperGridView.itemSize + spacing: Style.marginXS * scaling + + Rectangle { + id: imageContainer + Layout.fillWidth: true + Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67) + color: Color.transparent + + NImageCached { + id: img + imagePath: wallpaperPath + anchors.fill: parent + } Rectangle { - id: imageContainer - Layout.fillWidth: true - Layout.preferredHeight: Math.round(wallpaperGridView.itemSize * 0.67) + anchors.fill: parent color: Color.transparent + border.color: isSelected ? Color.mSecondary : Color.mSurface + border.width: Math.max(1, Style.borderL * 1.5 * scaling) + } - NImageCached { - id: img - imagePath: wallpaperPath - anchors.fill: parent + Rectangle { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS * scaling + width: 28 * scaling + height: 28 * scaling + radius: width / 2 + color: Color.mSecondary + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: isSelected + + NIcon { + icon: "check" + font.pointSize: Style.fontSizeM * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSecondary + anchors.centerIn: parent } + } - Rectangle { - anchors.fill: parent - color: Color.transparent - border.color: isSelected ? Color.mSecondary : Color.mSurface - border.width: Math.max(1, Style.borderL * 1.5 * scaling) - } - - Rectangle { - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginS * scaling - width: 28 * scaling - height: 28 * scaling - radius: width / 2 - color: Color.mSecondary - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: isSelected - - NIcon { - icon: "check" - font.pointSize: Style.fontSizeM * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSecondary - anchors.centerIn: parent - } - } - - Rectangle { - anchors.fill: parent - color: Color.mSurface - opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3 - radius: parent.radius - Behavior on opacity { - NumberAnimation { - duration: Style.animationFast - } - } - } - - MouseArea { - id: mouseArea - anchors.fill: parent - acceptedButtons: Qt.LeftButton - hoverEnabled: true - onPressed: { - if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { - WallpaperService.changeWallpaper(wallpaperPath, undefined) - } else { - WallpaperService.changeWallpaper(wallpaperPath, Screen.name) - } + Rectangle { + anchors.fill: parent + color: Color.mSurface + opacity: (mouseArea.containsMouse || isSelected) ? 0 : 0.3 + radius: parent.radius + Behavior on opacity { + NumberAnimation { + duration: Style.animationFast } } } - NText { - text: filename - color: Color.mOnSurfaceVariant - opacity: 0.5 - font.pointSize: Style.fontSizeXS * scaling - Layout.fillWidth: true - Layout.leftMargin: Style.marginS * scaling - Layout.rightMargin: Style.marginS * scaling - Layout.alignment: Qt.AlignHCenter - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight + MouseArea { + id: mouseArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + hoverEnabled: true + onPressed: { + if (Settings.data.wallpaper.setWallpaperOnAllMonitors) { + WallpaperService.changeWallpaper(wallpaperPath, undefined) + } else { + WallpaperService.changeWallpaper(wallpaperPath, targetScreen.name) + } + } } } + + NText { + text: filename + color: Color.mOnSurfaceVariant + opacity: 0.5 + font.pointSize: Style.fontSizeXS * scaling + Layout.fillWidth: true + Layout.leftMargin: Style.marginS * scaling + Layout.rightMargin: Style.marginS * scaling + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } } } + } - // Empty / scanning state - Rectangle { - color: Color.mSurface - radius: Style.radiusM * scaling - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS * scaling) - visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning - Layout.fillWidth: true - Layout.preferredHeight: 130 * scaling + // Empty / scanning state + Rectangle { + color: Color.mSurface + radius: Style.radiusM * scaling + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: (filteredWallpapers.length === 0 && !WallpaperService.scanning) || WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: 130 * scaling - ColumnLayout { - anchors.fill: parent - visible: WallpaperService.scanning - NBusyIndicator { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } + ColumnLayout { + anchors.fill: parent + visible: WallpaperService.scanning + NBusyIndicator { + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter } + } - ColumnLayout { - anchors.fill: parent - visible: filteredWallpapers.length === 0 && !WallpaperService.scanning - Item { - Layout.fillHeight: true - } - NIcon { - icon: "folder-open" - font.pointSize: Style.fontSizeXXL * scaling - color: Color.mOnSurface - Layout.alignment: Qt.AlignHCenter - } - NText { - text: (filterText && filterText.length > 0) ? "No match found." : "No wallpaper found." - color: Color.mOnSurface - font.weight: Style.fontWeightBold - Layout.alignment: Qt.AlignHCenter - } - NText { - text: (filterText && filterText.length > 0) ? "Try a different search query." : "Configure your wallpaper directory with images." - color: Color.mOnSurfaceVariant - wrapMode: Text.WordWrap - Layout.alignment: Qt.AlignHCenter - } - Item { - Layout.fillHeight: true - } + ColumnLayout { + anchors.fill: parent + visible: filteredWallpapers.length === 0 && !WallpaperService.scanning + Item { + Layout.fillHeight: true + } + NIcon { + icon: "folder-open" + font.pointSize: Style.fontSizeXXL * scaling + color: Color.mOnSurface + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (wallpaperPanel.filterText && wallpaperPanel.filterText.length > 0) ? "No match found." : "No wallpaper found." + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } + NText { + text: (wallpaperPanel.filterText && wallpaperPanel.filterText.length > 0) ? "Try a different search query." : "Configure your wallpaper directory with images." + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } + Item { + Layout.fillHeight: true } } }