fix(glass): stabilize optical surfaces and page transitions (#594)

This commit is contained in:
InfinityPacer
2026-07-27 17:33:05 +08:00
committed by GitHub
parent 15ef332ea2
commit 3daa031a04
16 changed files with 429 additions and 80 deletions
@@ -1,5 +1,6 @@
import {
BACKGROUND_ROTATION_GRACE_MS,
createBackgroundCandidateOrderResolver,
findFirstAvailableBackground,
preloadBackgroundRotationImages,
shouldAllowBackgroundRotation,
@@ -27,6 +28,27 @@ describe('background rotation lifecycle', () => {
})
})
describe('createBackgroundCandidateOrderResolver', () => {
it('shuffles a source list without mutating the backend response', () => {
const urls = ['one.jpg', 'two.jpg', 'three.jpg']
const resolveOrder = createBackgroundCandidateOrderResolver(() => 0)
expect(resolveOrder(urls)).toEqual(['two.jpg', 'three.jpg', 'one.jpg'])
expect(urls).toEqual(['one.jpg', 'two.jpg', 'three.jpg'])
})
it('keeps the same order across retries and reshuffles only after the source list changes', () => {
const random = vi.fn().mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValueOnce(0.5)
const resolveOrder = createBackgroundCandidateOrderResolver(random)
const firstOrder = resolveOrder(['one.jpg', 'two.jpg', 'three.jpg'])
expect(resolveOrder(['one.jpg', 'two.jpg', 'three.jpg'])).toEqual(firstOrder)
expect(random).toHaveBeenCalledTimes(2)
expect(resolveOrder(['one.jpg', 'two.jpg'])).toEqual(['one.jpg', 'two.jpg'])
expect(random).toHaveBeenCalledTimes(3)
})
})
describe('preloadBackgroundRotationImages', () => {
it('does not let an unused optical texture block the visible wallpaper', async () => {
const preload = vi.fn(async (url: string) => url === 'display.jpg')
+27 -2
View File
@@ -3,11 +3,36 @@ import type { AppActivityState } from '@/utils/appActivityLifecycle'
/** 壁纸在窗口失焦后继续轮换的最长时间,交互 renderer 仍由应用生命周期独立暂停。 */
export const BACKGROUND_ROTATION_GRACE_MS = 60_000
type RandomSource = () => number
/** 壁纸轮换只在前台活动或失焦宽限期内运行,系统减少动态效果时始终停止。 */
export function shouldAllowBackgroundRotation(state: AppActivityState, graceActive: boolean, reducedMotion: boolean) {
return !reducedMotion && (state === 'active' || graceActive)
}
/**
* 为每个前端页面生命周期生成稳定的随机候选顺序;相同来源列表在重试和状态恢复时不会再次洗牌。
*/
export function createBackgroundCandidateOrderResolver(random: RandomSource = Math.random) {
let sourceUrls: string[] | null = null
let orderedUrls: string[] = []
return (urls: string[]) => {
const sourceChanged =
!sourceUrls || sourceUrls.length !== urls.length || sourceUrls.some((url, index) => url !== urls[index])
if (!sourceChanged) return [...orderedUrls]
sourceUrls = [...urls]
orderedUrls = [...urls]
for (let index = orderedUrls.length - 1; index > 0; index -= 1) {
const swapIndex = Math.floor(random() * (index + 1))
;[orderedUrls[index], orderedUrls[swapIndex]] = [orderedUrls[swapIndex], orderedUrls[index]]
}
return [...orderedUrls]
}
}
interface BackgroundRotationImagePreloadOptions {
/** 外层背景实际显示的壁纸地址。 */
displayUrl: string
@@ -18,7 +43,7 @@ interface BackgroundRotationImagePreloadOptions {
}
interface FirstAvailableBackgroundOptions {
/** 保持后端返回顺序的候选壁纸。 */
/** 当前页面生命周期已确定顺序的候选壁纸。 */
urls: string[]
/** 当前加载批次仍可提交时返回 true。 */
canContinue: () => boolean
@@ -26,7 +51,7 @@ interface FirstAvailableBackgroundOptions {
preload: (url: string) => Promise<boolean>
}
/** 按来源顺序寻找首张可用壁纸,单项失败或过期批次不会提交可见状态。 */
/** 按当前候选顺序寻找首张可用壁纸,单项失败或过期批次不会提交可见状态。 */
export async function findFirstAvailableBackground(options: FirstAvailableBackgroundOptions) {
for (let index = 0; index < options.urls.length; index += 1) {
if (!options.canContinue()) return null
+5
View File
@@ -72,9 +72,14 @@ export interface GlassOpticalSpringState {
velocity: number
}
/** 光学表面在共享 renderer 中采用的动态响应合同。 */
export type GlassOpticalSurfaceMode = 'dynamic' | 'static-material'
export interface GlassOpticalSurfaceCandidate<TKey> {
/** renderer 生命周期内稳定的表面身份。 */
key: TKey
/** 表面使用完整动态光学,或只保留稳定材质能量。 */
mode?: GlassOpticalSurfaceMode
/** 表面的当前视口几何。 */
rect: GlassOpticalRect
}