From 1bd093db7fcc8f15404d2219dd7fe0a04a348f33 Mon Sep 17 00:00:00 2001 From: Ly-sec Date: Thu, 18 Sep 2025 17:55:30 +0200 Subject: [PATCH] WallpaperSelector overhaul: initial commit --- Commons/Settings.qml | 3 + Modules/SettingsPanel/SettingsPanel.qml | 19 +- Modules/SidePanel/Cards/UtilitiesCard.qml | 10 +- .../WallpaperSelectorPanel.qml | 282 ++++++++++++++++++ Services/WallpaperService.qml | 14 + shell.qml | 6 + 6 files changed, 310 insertions(+), 24 deletions(-) create mode 100644 Modules/WallpaperSelectorPanel/WallpaperSelectorPanel.qml diff --git a/Commons/Settings.qml b/Commons/Settings.qml index ed6b8499e..3f909c530 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -177,6 +177,9 @@ Singleton { MatugenService.init() + // Ensure wallpapers are restored after settings have been loaded + WallpaperService.init() + FontService.init() HooksService.init() diff --git a/Modules/SettingsPanel/SettingsPanel.qml b/Modules/SettingsPanel/SettingsPanel.qml index 887954d3b..55bef4a9f 100644 --- a/Modules/SettingsPanel/SettingsPanel.qml +++ b/Modules/SettingsPanel/SettingsPanel.qml @@ -36,8 +36,7 @@ NPanel { Network, Notification, ScreenRecorder, - Wallpaper, - WallpaperSelector + Wallpaper } property int requestedTab: SettingsPanel.Tab.General @@ -92,10 +91,7 @@ NPanel { id: wallpaperTab Tabs.WallpaperTab {} } - Component { - id: wallpaperSelectorTab - Tabs.WallpaperSelectorTab {} - } + Component { id: screenRecorderTab Tabs.ScreenRecorderTab {} @@ -176,16 +172,7 @@ NPanel { "source": wallpaperTab }] - // Only add the Wallpaper Selector tab if the feature is enabled - if (Settings.data.wallpaper.enabled) { - newTabs.push({ - "id": SettingsPanel.Tab.WallpaperSelector, - "label": "Wallpaper Selector", - "icon": "settings-wallpaper-selector", - "source": wallpaperSelectorTab - }) - } - + // Wallpaper selector moved to its own panel newTabs.push({ "id": SettingsPanel.Tab.ScreenRecorder, "label": "Screen Recorder", diff --git a/Modules/SidePanel/Cards/UtilitiesCard.qml b/Modules/SidePanel/Cards/UtilitiesCard.qml index fd23adf08..9be59e3fa 100644 --- a/Modules/SidePanel/Cards/UtilitiesCard.qml +++ b/Modules/SidePanel/Cards/UtilitiesCard.qml @@ -55,14 +55,8 @@ NBox { visible: Settings.data.wallpaper.enabled icon: "wallpaper-selector" tooltipText: "Left click: Open wallpaper selector.\nRight click: Set random wallpaper." - onClicked: { - var settingsPanel = PanelService.getPanel("settingsPanel") - settingsPanel.requestedTab = SettingsPanel.Tab.WallpaperSelector - settingsPanel.open() - } - onRightClicked: { - WallpaperService.setRandomWallpaper() - } + onClicked: PanelService.getPanel("wallpaperSelectorPanel")?.toggle(this) + onRightClicked: WallpaperService.setRandomWallpaper() } Item { diff --git a/Modules/WallpaperSelectorPanel/WallpaperSelectorPanel.qml b/Modules/WallpaperSelectorPanel/WallpaperSelectorPanel.qml new file mode 100644 index 000000000..33a16b27d --- /dev/null +++ b/Modules/WallpaperSelectorPanel/WallpaperSelectorPanel.qml @@ -0,0 +1,282 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services +import qs.Widgets + +NPanel { + id: root + + preferredWidth: 640 + preferredHeight: 480 + preferredWidthRatio: 0.4 + preferredHeightRatio: 0.5 + panelAnchorHorizontalCenter: true + panelAnchorVerticalCenter: true + panelKeyboardFocus: true + + // Local reactive state + property list wallpapersList: [] + property string currentWallpaper: "" + + function refreshForScreen() { + const name = Screen.name + wallpapersList = WallpaperService.getWallpapersList(name) + currentWallpaper = WallpaperService.getWallpaper(name) + } + + onOpened: refreshForScreen() + + Connections { + target: WallpaperService + function onWallpaperChanged(screenName, path) { + if (screenName === Screen.name) { + currentWallpaper = WallpaperService.getWallpaper(Screen.name) + } + } + function onWallpaperDirectoryChanged(screenName, directory) { + if (screenName === Screen.name) { + refreshForScreen() + } + } + function onWallpaperListChanged(screenName, count) { + if (screenName === Screen.name) { + refreshForScreen() + } + } + } + + panelContent: Rectangle { + color: Color.transparent + + ColumnLayout { + anchors.fill: parent + anchors.margins: Style.marginL * scaling + spacing: Style.marginM * scaling + + // Header + RowLayout { + Layout.fillWidth: true + spacing: Style.marginM * scaling + + NIcon { + icon: "settings-wallpaper-selector" + font.pointSize: Style.fontSizeXXL * scaling + color: Color.mPrimary + } + + NText { + text: "Wallpaper Selector" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.fillWidth: true + } + + NIconButton { + icon: "refresh" + tooltipText: "Refresh wallpaper list" + baseSize: Style.baseWidgetSize * 0.8 + onClicked: WallpaperService.refreshWallpapersList() + } + + NIconButton { + icon: "close" + tooltipText: "Close." + baseSize: Style.baseWidgetSize * 0.8 + onClicked: root.close() + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + + // Scroll container mirrors SettingsPanel to avoid overflow and keep interactions smooth + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + pressDelay: 200 + + NScrollView { + id: scrollView + anchors.fill: parent + horizontalPolicy: ScrollBar.AlwaysOff + verticalPolicy: ScrollBar.AsNeeded + padding: Style.marginL * 0 * scaling + clip: true + + ColumnLayout { + width: scrollView.availableWidth + spacing: Style.marginM * scaling + + // Selector header removed (title and refresh are redundant here) + NToggle { + label: "Apply to all monitors" + 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) + } + + // Grid container + Item { + visible: !WallpaperService.scanning + Layout.fillWidth: true + Layout.preferredHeight: Math.ceil(wallpapersList.length / wallpaperGridView.columns) * wallpaperGridView.cellHeight + + GridView { + id: wallpaperGridView + anchors.fill: parent + model: wallpapersList + interactive: false + clip: true + + property int columns: 5 + 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.67) + Style.marginS * scaling + + leftMargin: Style.marginS * scaling + rightMargin: Style.marginS * scaling + topMargin: Style.marginS * scaling + bottomMargin: Style.marginS * scaling + + delegate: Rectangle { + id: wallpaperItem + + property string wallpaperPath: modelData + property bool isSelected: (wallpaperPath === currentWallpaper) + + width: wallpaperGridView.itemSize + height: Math.round(wallpaperGridView.itemSize * 0.67) + color: Color.transparent + + NImageCached { + id: img + imagePath: wallpaperPath + anchors.fill: 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) + } + } + } + } + } + } + + // Empty / scanning state + Rectangle { + color: Color.mSurface + radius: Style.radiusM * scaling + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS * scaling) + visible: wallpapersList.length === 0 || 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: wallpapersList.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: "No wallpaper found." + color: Color.mOnSurface + font.weight: Style.fontWeightBold + Layout.alignment: Qt.AlignHCenter + } + NText { + text: "Configure your wallpaper directory with images." + color: Color.mOnSurfaceVariant + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignHCenter + } + Item { + Layout.fillHeight: true + } + } + } + + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + } + } + } + } + } +} diff --git a/Services/WallpaperService.qml b/Services/WallpaperService.qml index 3a0756e50..b09a78393 100644 --- a/Services/WallpaperService.qml +++ b/Services/WallpaperService.qml @@ -9,6 +9,20 @@ import qs.Commons Singleton { id: root + // Public init to rehydrate cache after Settings load + function init() { + // Rebuild cache from persisted settings + var monitors = Settings.data.wallpaper.monitors || [] + currentWallpapers = ({}) + for (var i = 0; i < monitors.length; i++) { + if (monitors[i].name && monitors[i].wallpaper) { + currentWallpapers[monitors[i].name] = monitors[i].wallpaper + // Notify listeners so Background updates immediately after settings load + root.wallpaperChanged(monitors[i].name, monitors[i].wallpaper) + } + } + } + Component.onCompleted: { Logger.log("Wallpaper", "Service started") diff --git a/shell.qml b/shell.qml index 24544f5f1..e8e00f780 100644 --- a/shell.qml +++ b/shell.qml @@ -28,6 +28,7 @@ import qs.Modules.PowerPanel import qs.Modules.SidePanel import qs.Modules.Toast import qs.Modules.WiFiPanel +import qs.Modules.WallpaperSelectorPanel import qs.Services import qs.Widgets @@ -94,6 +95,11 @@ ShellRoot { objectName: "bluetoothPanel" } + WallpaperSelectorPanel { + id: wallpaperSelectorPanel + objectName: "wallpaperSelectorPanel" + } + Component.onCompleted: { // Save a ref. to our lockScreen so we can access it easily PanelService.lockScreen = lockScreen