diff --git a/frontend/src/lib/search.ts b/frontend/src/lib/search.ts index 2b5f4e6..ab1545f 100644 --- a/frontend/src/lib/search.ts +++ b/frontend/src/lib/search.ts @@ -45,7 +45,13 @@ export const SEARCH_INDEX: SearchHit[] = [ { page: "appearance", label: "Wallpaper", - keywords: "wallpaper theme palette pywal breadpaper accent", + keywords: "wallpaper theme palette pywal breadpaper accent per monitor multi monitor background", + }, + { + page: "appearance", + tab: "shelltheme", + label: "Shell theme", + keywords: "shell theme breadbar breadbox bar launcher style liquid motion glass workbench spotlight daylight layout animation", }, { page: "appearance", diff --git a/frontend/src/lib/views/AppearanceHub.svelte b/frontend/src/lib/views/AppearanceHub.svelte index 692f3ea..e8e2e1f 100644 --- a/frontend/src/lib/views/AppearanceHub.svelte +++ b/frontend/src/lib/views/AppearanceHub.svelte @@ -1,6 +1,7 @@ @@ -9,6 +10,7 @@ lede="Wallpaper sets the colors. Windows follow." tabs={[ { id: "breadpaper", label: "Wallpaper", component: Breadpaper }, + { id: "shelltheme", label: "Shell theme", component: ShellTheme }, { id: "appearance", label: "Windows", component: Appearance }, ]} /> diff --git a/frontend/src/lib/views/Breadpaper.svelte b/frontend/src/lib/views/Breadpaper.svelte index d94cd39..b4abb31 100644 --- a/frontend/src/lib/views/Breadpaper.svelte +++ b/frontend/src/lib/views/Breadpaper.svelte @@ -4,25 +4,57 @@ import { open } from "@tauri-apps/plugin-dialog"; import ViewScaffold from "$lib/components/ViewScaffold.svelte"; import Group from "$lib/components/Group.svelte"; + import SwitchField from "$lib/components/SwitchField.svelte"; interface LibraryEntry { path: string; name: string; } + interface LiveMonitor { + name: string; + mode: string; + } + interface OutputWallpaper { + output: string; + path: string; + } + + let monitors = $state([]); + let byOutput = $state>({}); + let globalPath = $state(null); + let selected = $state(null); + let sameOnAll = $state(true); - let currentPath = $state(null); let status = $state(""); let libraryDir = $state(""); let library = $state(null); let scanning = $state(false); - async function refreshCurrent() { - currentPath = await invoke("get_current_wallpaper"); + // The wallpaper the current selection resolves to: the global one when + // "same on every monitor" is on, otherwise the selected monitor's own + // (falling back to the global if that monitor was never set explicitly). + let activePath = $derived( + sameOnAll ? globalPath : (selected && byOutput[selected]) || globalPath, + ); + + const perMonitor = $derived(monitors.length > 1); + + async function refresh() { + globalPath = await invoke("get_current_wallpaper"); + const list = await invoke("get_wallpapers_by_output"); + byOutput = Object.fromEntries(list.map((o) => [o.output, o.path])); + monitors = await invoke("get_live_monitors"); + if (!selected || !monitors.some((m) => m.name === selected)) { + selected = monitors[0]?.name ?? null; + } } onMount(async () => { libraryDir = await invoke("wallpaper_library_dir_display"); - await refreshCurrent(); + await refresh(); + // Start in per-monitor mode only if the monitors genuinely disagree. + const distinct = new Set(Object.values(byOutput)); + sameOnAll = distinct.size <= 1; scanning = true; try { library = await invoke("list_wallpaper_library"); @@ -34,8 +66,12 @@ async function apply(path: string) { status = "Setting…"; try { - await invoke("set_wallpaper", { path }); - await refreshCurrent(); + if (sameOnAll || !selected) { + await invoke("set_wallpaper", { path }); + } else { + await invoke("set_wallpaper_on", { output: selected, path }); + } + await refresh(); status = "Set"; } catch (e) { status = `${e}`; @@ -51,13 +87,39 @@ }); if (typeof path === "string") await apply(path); } + + function shortName(p: string | null): string { + return p ? (p.split("/").pop() ?? p) : ""; + } - - {#if currentPath} -
- {currentPath.split("/").pop()} + + {#if perMonitor} + + {/if} + + {#if perMonitor && !sameOnAll} +
+ {#each monitors as m (m.name)} + + {/each} +
+ {/if} + + {#if activePath} +
+ + {#if perMonitor && !sameOnAll}{selected} · {/if}{shortName(activePath)} +
{:else}
No wallpaper
@@ -78,7 +140,7 @@ {#each library as item (item.path)}