Files
MoviePilot-Frontend/src/components/theme/GlassOpticalLayer.vue

140 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>