mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-14 10:05:10 +08:00
140 lines
5.3 KiB
Vue
140 lines
5.3 KiB
Vue
<script setup lang="ts">
|
||
import type { ThemeCustomizerGlassAppearance, ThemeCustomizerGlassQuality } from '@/composables/useThemeCustomizer'
|
||
import { usePagePresentationMotion } from '@/composables/usePagePresentationMotion'
|
||
import {
|
||
setGlassRendererState,
|
||
useGlassOpticalInteractionSource,
|
||
useGlassOpticalRenderer,
|
||
type GlassRendererState,
|
||
} from '@/composables/useGlassOpticalRenderer'
|
||
|
||
const props = defineProps<{
|
||
/** 当前玻璃材质,用于选择透明、色调或磨砂的光学参数。 */
|
||
appearance: ThemeCustomizerGlassAppearance
|
||
/** 用户选择的局部非均匀形变强度。 */
|
||
deformationStrength: number
|
||
/** 用户选择的轨迹、尾波与惯性强度。 */
|
||
flowStrength: number
|
||
/** 当前光学质量;标准档不会挂载该组件。 */
|
||
quality: Exclude<ThemeCustomizerGlassQuality, 'css'>
|
||
/** 用户选择的亮边、镜面高光与焦散强度。 */
|
||
reflectionStrength: number
|
||
/** 用户选择的真实壁纸可见度。 */
|
||
transparencyStrength: number
|
||
/** 玻璃内部壁纸采样的透射亮度;不会改变外层壁纸的曝光合同。 */
|
||
transmissionStrength: number
|
||
/** 用户选择的共享壁纸采样平移强度。 */
|
||
translationStrength: number
|
||
/** 路由变化标识,用于在页面内容稳定后重新发现高价值表面。 */
|
||
routeKey: string
|
||
/** 当前主题主色,用于同步色调材质的光学高光。 */
|
||
tintColor: string
|
||
/** 外层壁纸交叉淡化的时长,shader 使用同一时钟混合双纹理。 */
|
||
transitionDuration: number
|
||
/** 外层壁纸交叉淡化的 performance timeline 起点。 */
|
||
transitionStartedAt: number
|
||
/** 与 CSS 背景保持一致的活动壁纸。 */
|
||
wallpaperUrl: string
|
||
/** 切换期保留的上一张壁纸;空值表示当前没有交叉淡化。 */
|
||
previousWallpaperUrl: string
|
||
/** 下一张同源壁纸;两个 context 均完成上传后才允许外层提交切换。 */
|
||
pendingWallpaperUrl?: string
|
||
}>()
|
||
const emit = defineEmits<{
|
||
/** fixed 与 scroll renderer 均已准备同一张待切换纹理。 */
|
||
wallpaperPrepared: [url: string]
|
||
}>()
|
||
|
||
const fixedCanvas = ref<HTMLCanvasElement | null>(null)
|
||
const scrollCanvas = ref<HTMLCanvasElement | null>(null)
|
||
const interactionSource = useGlassOpticalInteractionSource()
|
||
const pagePresentationMotion = usePagePresentationMotion()
|
||
const fixedRenderer = useGlassOpticalRenderer({
|
||
active: true,
|
||
appearance: () => props.appearance,
|
||
canvas: fixedCanvas,
|
||
deformationStrength: () => props.deformationStrength,
|
||
flowStrength: () => props.flowStrength,
|
||
interactionSource,
|
||
quality: () => props.quality,
|
||
reflectionStrength: () => props.reflectionStrength,
|
||
transparencyStrength: () => props.transparencyStrength,
|
||
transmissionStrength: () => props.transmissionStrength,
|
||
translationStrength: () => props.translationStrength,
|
||
routeKey: () => props.routeKey,
|
||
tintColor: () => props.tintColor,
|
||
transitionDuration: () => props.transitionDuration,
|
||
transitionStartedAt: () => props.transitionStartedAt,
|
||
wallpaperUrl: () => props.wallpaperUrl,
|
||
previousWallpaperUrl: () => props.previousWallpaperUrl,
|
||
pendingWallpaperUrl: () => props.pendingWallpaperUrl ?? '',
|
||
surfaceSpace: 'fixed',
|
||
syncDocumentState: false,
|
||
})
|
||
const scrollRenderer = useGlassOpticalRenderer({
|
||
active: true,
|
||
appearance: () => props.appearance,
|
||
canvas: scrollCanvas,
|
||
deformationStrength: () => props.deformationStrength,
|
||
flowStrength: () => props.flowStrength,
|
||
interactionSource,
|
||
pageMotion: pagePresentationMotion.reader,
|
||
quality: () => props.quality,
|
||
reflectionStrength: () => props.reflectionStrength,
|
||
transparencyStrength: () => props.transparencyStrength,
|
||
transmissionStrength: () => props.transmissionStrength,
|
||
translationStrength: () => props.translationStrength,
|
||
routeKey: () => props.routeKey,
|
||
tintColor: () => props.tintColor,
|
||
transitionDuration: () => props.transitionDuration,
|
||
transitionStartedAt: () => props.transitionStartedAt,
|
||
wallpaperUrl: () => props.wallpaperUrl,
|
||
previousWallpaperUrl: () => props.previousWallpaperUrl,
|
||
pendingWallpaperUrl: () => props.pendingWallpaperUrl ?? '',
|
||
surfaceSpace: 'scroll',
|
||
syncDocumentState: false,
|
||
})
|
||
|
||
const rendererState = ref<GlassRendererState>('loading')
|
||
|
||
/** 两个呈现 context 作为同一材质能力接管 CSS,避免部分就绪时出现混合材质。 */
|
||
watchEffect(() => {
|
||
const states = [fixedRenderer.state.value, scrollRenderer.state.value]
|
||
const state: GlassRendererState = states.every(value => value === 'ready')
|
||
? 'ready'
|
||
: states.some(value => value === 'loading')
|
||
? 'loading'
|
||
: 'fallback'
|
||
|
||
setGlassRendererState(rendererState, state)
|
||
})
|
||
|
||
watchEffect(() => {
|
||
const url = props.pendingWallpaperUrl
|
||
if (url && fixedRenderer.preparedWallpaperUrl.value === url && scrollRenderer.preparedWallpaperUrl.value === url) {
|
||
emit('wallpaperPrepared', url)
|
||
}
|
||
})
|
||
|
||
onScopeDispose(() => {
|
||
setGlassRendererState(rendererState, 'fallback')
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<canvas
|
||
ref="fixedCanvas"
|
||
class="glass-optical-layer glass-optical-layer--fixed"
|
||
aria-hidden="true"
|
||
data-presentation-space="fixed"
|
||
:data-state="fixedRenderer.state.value"
|
||
/>
|
||
<canvas
|
||
ref="scrollCanvas"
|
||
class="glass-optical-layer glass-optical-layer--scroll"
|
||
aria-hidden="true"
|
||
data-presentation-space="scroll"
|
||
:data-state="scrollRenderer.state.value"
|
||
/>
|
||
</template>
|