diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index 4e76466b0..88422ac01 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -2061,15 +2061,15 @@ "apikey-placeholder": "Enter your Wallhaven API Key", "appearance-dark-tab": "Dark", "appearance-light-tab": "Light", - "apply-all-monitors-description": "Apply the selected wallpaper to all monitors.", - "apply-all-monitors-label": "Apply to all monitors", + "header-devices-apply-all-tooltip": "Choose wallpaper per monitor", + "header-devices-per-monitor-tooltip": "Apply to all monitors", + "header-sun-linked-tooltip": "Separate light and dark wallpapers", + "header-sun-separate-tooltip": "Same wallpaper for light and dark", "categories-anime": "Anime", "categories-label": "Categories", "categories-people": "People", "color-extraction-disabled": "Use wallpaper color extraction", "color-extraction-enabled": "Use predefined color schemes", - "link-light-dark-description": "Use one wallpaper for both light and dark appearance. Turn off to pick different images.", - "link-light-dark-label": "Same wallpaper for light and dark", "order-asc": "Ascending", "order-desc": "Descending", "order-label": "Order", diff --git a/Modules/Panels/Wallpaper/WallpaperPanel.qml b/Modules/Panels/Wallpaper/WallpaperPanel.qml index 17414ecdb..e4a24083b 100644 --- a/Modules/Panels/Wallpaper/WallpaperPanel.qml +++ b/Modules/Panels/Wallpaper/WallpaperPanel.qml @@ -141,6 +141,8 @@ SmartPanel { property var currentScreen: Quickshell.screens[currentScreenIndex] property string filterText: "" property int appearanceTabIndex: 0 + readonly property bool headerScreensStripAvailable: !Settings.data.wallpaper.setWallpaperOnAllMonitors || Settings.data.wallpaper.enableMultiMonitorDirectories + readonly property bool headerDevicesButtonVisible: Quickshell.screens.length > 1 || Settings.data.wallpaper.enableMultiMonitorDirectories property alias screenRepeater: screenRepeater Component.onCompleted: { @@ -294,6 +296,26 @@ SmartPanel { Layout.fillWidth: true } + NIconButton { + visible: Settings.data.wallpaper.enabled + icon: "sun" + tooltipText: Settings.data.wallpaper.linkLightAndDarkWallpapers ? I18n.tr("wallpaper.panel.header-sun-linked-tooltip") : I18n.tr("wallpaper.panel.header-sun-separate-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + colorBg: !Settings.data.wallpaper.linkLightAndDarkWallpapers ? Color.mPrimary : Color.smartAlpha(Color.mSurfaceVariant) + colorFg: !Settings.data.wallpaper.linkLightAndDarkWallpapers ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.wallpaper.linkLightAndDarkWallpapers = !Settings.data.wallpaper.linkLightAndDarkWallpapers + } + + NIconButton { + visible: Settings.data.wallpaper.enabled && panelContent.headerDevicesButtonVisible + icon: "devices" + tooltipText: Settings.data.wallpaper.setWallpaperOnAllMonitors ? I18n.tr("wallpaper.panel.header-devices-apply-all-tooltip") : I18n.tr("wallpaper.panel.header-devices-per-monitor-tooltip") + baseSize: Style.baseWidgetSize * 0.8 + colorBg: !Settings.data.wallpaper.setWallpaperOnAllMonitors ? Color.mPrimary : Color.smartAlpha(Color.mSurfaceVariant) + colorFg: !Settings.data.wallpaper.setWallpaperOnAllMonitors ? Color.mOnPrimary : Color.mPrimary + onClicked: Settings.data.wallpaper.setWallpaperOnAllMonitors = !Settings.data.wallpaper.setWallpaperOnAllMonitors + } + NIconButton { icon: "palette" tooltipText: I18n.tr("wallpaper.panel.solid-color-tooltip") @@ -326,23 +348,6 @@ SmartPanel { Layout.fillWidth: true } - NToggle { - label: I18n.tr("wallpaper.panel.apply-all-monitors-label") - description: I18n.tr("wallpaper.panel.apply-all-monitors-description") - checked: Settings.data.wallpaper.setWallpaperOnAllMonitors - onToggled: checked => Settings.data.wallpaper.setWallpaperOnAllMonitors = checked - Layout.fillWidth: true - } - - NToggle { - label: I18n.tr("wallpaper.panel.link-light-dark-label") - description: I18n.tr("wallpaper.panel.link-light-dark-description") - checked: Settings.data.wallpaper.linkLightAndDarkWallpapers - onToggled: checked => Settings.data.wallpaper.linkLightAndDarkWallpapers = checked - defaultValue: Settings.getDefaultValue("wallpaper.linkLightAndDarkWallpapers") - Layout.fillWidth: true - } - NTabBar { id: appearanceTabBar visible: Settings.data.wallpaper.enabled && !Settings.data.wallpaper.linkLightAndDarkWallpapers @@ -372,10 +377,9 @@ SmartPanel { } } - // Monitor tabs NTabBar { id: screenTabBar - visible: (!Settings.data.wallpaper.setWallpaperOnAllMonitors || Settings.data.wallpaper.enableMultiMonitorDirectories) + visible: panelContent.headerScreensStripAvailable Layout.fillWidth: true currentIndex: currentScreenIndex onCurrentIndexChanged: currentScreenIndex = currentIndex @@ -685,31 +689,19 @@ SmartPanel { property bool isBrowseMode: Settings.data.wallpaper.viewMode === "browse" property int _browseScanGeneration: 0 - // Order: (1) favorites for the active Light/Dark tab, (2) favorites only for the other appearance, - // (3) everything else. Legacy favorites without "appearance" still match via WallpaperService (darkMode on entry). + // All favorited wallpapers (any light/dark slot) first, then the rest function sortFavoritesToTop(items) { - var forThisAppearance = []; - var forOtherAppearance = []; + var favs = []; var rest = []; - var app = WallpaperService.wallpaperSelectionAppearance; for (var i = 0; i < items.length; i++) { var it = items[i]; - if (!it.isDirectory) { - var path = it.path; - var here = WallpaperService.isFavorite(path, app); - var any = WallpaperService.isFavorite(path); - if (here) { - forThisAppearance.push(it); - } else if (any) { - forOtherAppearance.push(it); - } else { - rest.push(it); - } + if (!it.isDirectory && WallpaperService.isFavorite(it.path)) { + favs.push(it); } else { rest.push(it); } } - return forThisAppearance.concat(forOtherAppearance).concat(rest); + return favs.concat(rest); } // Rebuild filteredItems and sync to wallpaperModel (full replacement, no animation). @@ -1130,7 +1122,10 @@ SmartPanel { property string wallpaperPath: model.path ?? "" property bool isDirectory: model.isDirectory ?? false property bool isSelected: !isDirectory && (wallpaperPath === currentWallpaper) - property bool isFavorited: !isDirectory && WallpaperService.isFavorite(wallpaperPath, WallpaperService.wallpaperSelectionAppearance) + property bool isFavorited: { + WallpaperService.favoritesRevision; + return !isDirectory && WallpaperService.isFavorite(wallpaperPath); + } property string filename: model.name ?? wallpaperPath.split('/').pop() property string cachedPath: "" @@ -1312,10 +1307,13 @@ SmartPanel { property int _favRevision: 0 property var favData: { _favRevision; + WallpaperService.favoritesRevision; WallpaperService.wallpaperSelectionAppearance; - return WallpaperService.getFavorite(wallpaperItem.wallpaperPath, WallpaperService.wallpaperSelectionAppearance); + Settings.data.wallpaper.linkLightAndDarkWallpapers; + return WallpaperService.getFavoriteForDisplay(wallpaperItem.wallpaperPath); } property var colors: favData && favData.paletteColors ? favData.paletteColors : [] + readonly property bool showAppearanceSlotBadge: !Settings.data.wallpaper.linkLightAndDarkWallpapers property bool isDark: { if (!favData) { return false; @@ -1337,11 +1335,12 @@ SmartPanel { } } - // Dark/light mode indicator + // Light/dark slot (only when wallpapers differ per appearance — hidden when linked) Rectangle { width: paletteRow.diameter height: paletteRow.diameter radius: width * 0.5 + visible: paletteRow.showAppearanceSlotBadge color: Color.mSurface border.color: Color.mShadow border.width: Style.borderS diff --git a/Services/UI/WallpaperService.qml b/Services/UI/WallpaperService.qml index 8ca2c1064..d8d1d3e6f 100644 --- a/Services/UI/WallpaperService.qml +++ b/Services/UI/WallpaperService.qml @@ -59,6 +59,9 @@ Singleton { // Wallpaper panel: which appearance slot (light/dark) new selections apply to — like picking a monitor tab property string wallpaperSelectionAppearance: "light" + // Bumped when favorites are added/removed so grid delegates can refresh star state + property int favoritesRevision: 0 + // Signal emitted when browse path changes for a screen signal browsePathChanged(string screenName, string path) @@ -1329,6 +1332,28 @@ Singleton { return Settings.data.wallpaper.favorites[favoriteIndex]; } + // Palette / UI for a favorited path. When light and dark use the same wallpaper, pick one stable entry (no slot switching). + function getFavoriteForDisplay(path) { + if (Settings.data.wallpaper.linkLightAndDarkWallpapers) { + var anyIdx = _findAnyFavoriteIndexForPath(path); + if (anyIdx !== _favoriteNotFound) { + return Settings.data.wallpaper.favorites[anyIdx]; + } + return null; + } + var slot = _normalizeAppearanceSlot(root.wallpaperSelectionAppearance); + var idx = _findFavoriteIndex(path, slot); + if (idx !== _favoriteNotFound) { + return Settings.data.wallpaper.favorites[idx]; + } + var otherSlot = slot === "dark" ? "light" : "dark"; + idx = _findFavoriteIndex(path, otherSlot); + if (idx !== _favoriteNotFound) { + return Settings.data.wallpaper.favorites[idx]; + } + return null; + } + // ------------------------------------------------------------------- function toggleFavorite(path, appearanceSlot) { var slot; @@ -1349,6 +1374,7 @@ Singleton { } Settings.data.wallpaper.favorites = favorites; + root.favoritesRevision++; favoritesChanged(path); }