Merge branch 'themes-v2' of https://git.ez-pp.farm/EZPPFarm/EZPPLauncher into themes-v2

This commit is contained in:
HorizonCode committed 2026-11-04 21:44:22 +01:00
commit 575da8504a
5 files changed
+113 -9

No files matched your search

+32 -1
View File
@@ -162,7 +162,10 @@
-ms-user-select: none;
user-select: none;
}
html {
html,
body {
height: 100%;
overflow: hidden;
overscroll-behavior: none;
}
body {
@@ -197,3 +200,31 @@ video::-webkit-media-controls-overlay-enclosure {
width: 0 !important;
height: 0 !important;
}
/* Crossfade the rendered page when switching themes via the View Transitions API. */
::view-transition-old(root) {
animation: 320ms cubic-bezier(0.4, 0, 0.2, 1) both theme-crossfade-out;
}
::view-transition-new(root) {
animation: 320ms cubic-bezier(0.4, 0, 0.2, 1) both theme-crossfade-in;
}
@keyframes theme-crossfade-out {
to {
opacity: 0;
}
}
@keyframes theme-crossfade-in {
from {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation: none !important;
}
}
+42 -3
View File
@@ -16,6 +16,34 @@
const iconSet = $derived($active_custom_theme?.definition?.iconSet ?? DEFAULT_THEME_ICON_SET);
let volumeOpen = $state(false);
let volumeDragging = $state(false);
let volumeControl: HTMLDivElement | undefined = $state();
function openVolume() {
volumeOpen = true;
}
function closeVolume() {
if (!volumeDragging) volumeOpen = false;
}
function startVolumeDrag(event: PointerEvent) {
if (event.button !== 0) return;
volumeDragging = true;
volumeOpen = true;
}
function endVolumeDrag(event: PointerEvent) {
if (!volumeDragging) return;
volumeDragging = false;
const element = document.elementFromPoint(event.clientX, event.clientY);
if (!element || !volumeControl?.contains(element)) {
volumeOpen = false;
}
}
onMount(() => {
const appWindow = getCurrentWindow();
@@ -26,9 +54,18 @@
});
</script>
<svelte:window onpointerup={endVolumeDrag} onpointercancel={endVolumeDrag} />
<div data-tauri-drag-region class="titlebar z-[100]" class:macos-window={$platform === 'macos'}>
{#if $custom_theme_audio_playing || ($active_custom_theme && $active_custom_theme.name !== 'Default')}
<div class="relative flex items-center justify-center group">
<div
class="relative flex items-center justify-center group"
role="group"
aria-label="Theme volume"
bind:this={volumeControl}
onpointerenter={openVolume}
onpointerleave={closeVolume}
>
<div class="relative flex items-center justify-center p-1 cursor-pointer">
<div
class="absolute inset-0 bg-background rounded
@@ -51,8 +88,9 @@
<div
class="absolute top-full right-0 translate-x-[7px]
opacity-0 invisible pointer-events-none -translate-y-2
group-hover:opacity-100 group-hover:visible group-hover:pointer-events-auto group-hover:translate-y-0
{volumeOpen
? 'opacity-100 visible pointer-events-auto translate-y-0'
: 'opacity-0 invisible pointer-events-none -translate-y-2'}
{$reduceAnimations ? '' : 'transition-all duration-200 ease-out'}
z-50"
>
@@ -68,6 +106,7 @@
max="1"
step="0.01"
bind:value={$custom_theme_volume}
onpointerdown={startVolumeDrag}
oninput={() => {
config.value<number>('volume').set($custom_theme_volume);
}}
+35 -1
View File
@@ -7,6 +7,7 @@ import { convertFileSrc, invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import * as path from '@tauri-apps/api/path';
import * as fs from '@tauri-apps/plugin-fs';
import { tick } from 'svelte';
export type Theme = {
folder_name: string;
@@ -486,7 +487,18 @@ export const downloadTheme = async (
return { success: true };
};
export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volume = 0.15) => {
type LoadThemeOptions = {
/** Crossfade the rendered page when switching themes. */
animate?: boolean;
};
export const loadTheme = async (
theme: Theme,
themeContainer: HTMLElement,
volume = 0.15,
options: LoadThemeOptions = {}
) => {
const applyTheme = async () => {
// Theme media is buffered as blob URLs; the previous theme's URLs are no
// longer needed once it is replaced.
clearThemeMediaCache();
@@ -514,6 +526,28 @@ export const loadTheme = async (theme: Theme, themeContainer: HTMLElement, volum
});
void invoke('unlock_media_autoplay').catch(() => {});
};
const canAnimate =
options.animate &&
typeof document !== 'undefined' &&
typeof document.startViewTransition === 'function';
if (!canAnimate) {
await applyTheme();
return;
}
const transition = document.startViewTransition(async () => {
await applyTheme();
// Let Svelte paint the new theme before the transition takes its snapshot.
await tick();
});
// Theme activation must not wait for, or fail because of, the visual
// transition. `finished` can reject when a transition is skipped.
void transition.finished.catch(() => {});
await transition.updateCallbackDone;
};
export const deleteTheme = async (themeToUninstall: Theme) => {
+1
View File
@@ -583,6 +583,7 @@
</AlertDialog.Root>
<main
class="relative h-screen w-full overflow-hidden"
use:appActivity
use:themeMediaSuspension
use:themeParallax={{
+3 -4
View File
@@ -526,7 +526,9 @@
return;
}
loadTheme(theme, $custom_theme_container, $custom_theme_volume);
await loadTheme(theme, $custom_theme_container, $custom_theme_volume, {
animate: !$reduceAnimations,
});
await config.value<string>('theme').set(theme.name);
};
@@ -4511,9 +4513,6 @@
<div
class="absolute top-1.5 left-1.5 flex max-w-[calc(100%-4.5rem)] flex-wrap items-center gap-1"
>
{#if isActive}
<Badge class="text-[9px]">In use</Badge>
{/if}
{#if theme.name !== 'Default' && theme.folder_name !== 'default' && theme.apiVersion}
<Badge
variant="secondary"