feat(glass): unify login and app optical presentation (#591)

This commit is contained in:
InfinityPacer
2026-07-27 10:26:46 +08:00
committed by GitHub
parent 0f8a817413
commit 3886909e9e
21 changed files with 1561 additions and 628 deletions
+42 -2
View File
@@ -39,6 +39,7 @@ const draftFlowStrength = ref(settings.value.glassFlowStrength)
const draftPreset = ref<GlassOpticalPreset>(settings.value.glassPreset)
const draftQuality = ref<ThemeCustomizerGlassQuality>(settings.value.glassQuality)
const draftReflectionStrength = ref(settings.value.glassReflectionStrength)
const draftTransmissionStrength = ref(settings.value.glassTransmissionStrength)
const draftTranslationStrength = ref(settings.value.glassTranslationStrength)
const draftTransparencyStrength = ref(settings.value.glassTransparencyStrength)
const isSaving = ref(false)
@@ -69,6 +70,7 @@ watch(
draftPreset.value = settings.value.glassPreset
draftQuality.value = settings.value.glassQuality
draftReflectionStrength.value = settings.value.glassReflectionStrength
draftTransmissionStrength.value = settings.value.glassTransmissionStrength
draftTranslationStrength.value = settings.value.glassTranslationStrength
draftTransparencyStrength.value = settings.value.glassTransparencyStrength
} else if (previous) {
@@ -122,13 +124,14 @@ function updateQuality(value: unknown) {
previewGlassSettings({ glassQuality: option.value })
}
/** 将个具体参数作为一个预览事务同步,预置只负责生成这些值。 */
/** 将个具体参数作为一个预览事务同步,预置只负责生成这些值。 */
function previewDraftParameters() {
previewGlassSettings({
glassDeformationStrength: draftDeformationStrength.value,
glassFlowStrength: draftFlowStrength.value,
glassPreset: draftPreset.value,
glassReflectionStrength: draftReflectionStrength.value,
glassTransmissionStrength: draftTransmissionStrength.value,
glassTranslationStrength: draftTranslationStrength.value,
glassTransparencyStrength: draftTransparencyStrength.value,
})
@@ -144,6 +147,7 @@ function applyPreset(value: unknown) {
draftDeformationStrength.value = parameters.deformation
draftFlowStrength.value = parameters.flow
draftReflectionStrength.value = parameters.reflection
draftTransmissionStrength.value = parameters.transmission
draftTranslationStrength.value = parameters.translation
draftTransparencyStrength.value = parameters.transparency
previewDraftParameters()
@@ -173,6 +177,12 @@ function updateReflectionStrength(value: unknown) {
previewGlassSettings({ glassReflectionStrength: draftReflectionStrength.value })
}
/** 将透射亮度限制为稳定范围并即时调整卡片内部壁纸的明暗。 */
function updateTransmissionStrength(value: unknown) {
draftTransmissionStrength.value = normalizeGlassOpticalStrength(Array.isArray(value) ? value[0] : value)
previewGlassSettings({ glassTransmissionStrength: draftTransmissionStrength.value })
}
/** 将通透度限制为稳定范围并即时调整材质与真实壁纸的占比。 */
function updateTransparencyStrength(value: unknown) {
draftTransparencyStrength.value = normalizeGlassOpticalStrength(Array.isArray(value) ? value[0] : value)
@@ -198,6 +208,7 @@ async function saveSettings() {
glassPreset: draftPreset.value,
glassQuality: draftQuality.value,
glassReflectionStrength: draftReflectionStrength.value,
glassTransmissionStrength: draftTransmissionStrength.value,
glassTranslationStrength: draftTranslationStrength.value,
glassTransparencyStrength: draftTransparencyStrength.value,
})
@@ -292,6 +303,7 @@ onScopeDispose(cancelGlassPreview)
</section>
<section class="glass-settings-dialog__tuning">
<h3 class="glass-settings-dialog__group-label">{{ t('theme.glassMaterialTuning') }}</h3>
<div class="glass-settings-dialog__slider-header">
<h3 class="glass-settings-dialog__label">{{ t('theme.glassTransparencyStrength') }}</h3>
<output>{{ draftTransparencyStrength }}%</output>
@@ -309,6 +321,23 @@ onScopeDispose(cancelGlassPreview)
@update:model-value="updateTransparencyStrength"
/>
<div class="glass-settings-dialog__slider-header glass-settings-dialog__slider-header--spaced">
<h3 class="glass-settings-dialog__label">{{ t('theme.glassTransmissionStrength') }}</h3>
<output>{{ draftTransmissionStrength }}%</output>
</div>
<VSlider
:model-value="draftTransmissionStrength"
:aria-label="t('theme.glassTransmissionStrength')"
:min="GLASS_OPTICAL_STRENGTH_MIN"
:max="GLASS_OPTICAL_STRENGTH_MAX"
:step="1"
color="primary"
density="comfortable"
hide-details
thumb-label
@update:model-value="updateTransmissionStrength"
/>
<div class="glass-settings-dialog__slider-header glass-settings-dialog__slider-header--spaced">
<h3 class="glass-settings-dialog__label">{{ t('theme.glassReflectionStrength') }}</h3>
<output>{{ draftReflectionStrength }}%</output>
@@ -327,6 +356,7 @@ onScopeDispose(cancelGlassPreview)
/>
<div v-if="showsDynamicTuning" class="glass-settings-dialog__live-controls">
<h3 class="glass-settings-dialog__group-label">{{ t('theme.glassDynamicTuning') }}</h3>
<div class="glass-settings-dialog__slider-header">
<h3 class="glass-settings-dialog__label">{{ t('theme.glassTranslationStrength') }}</h3>
<output>{{ draftTranslationStrength }}%</output>
@@ -421,6 +451,14 @@ onScopeDispose(cancelGlassPreview)
line-height: 1.45;
}
.glass-settings-dialog__group-label {
margin: 0 0 14px;
color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
font-size: 0.75rem;
font-weight: 600;
line-height: 1.4;
}
.glass-settings-dialog__slider-header {
display: flex;
align-items: center;
@@ -444,7 +482,9 @@ onScopeDispose(cancelGlassPreview)
}
.glass-settings-dialog__live-controls {
margin-block-start: 18px;
border-block-start: 1px solid rgba(var(--v-theme-on-surface), 0.1);
margin-block-start: 24px;
padding-block-start: 20px;
:deep(.v-slider) {
margin-block-start: 4px;
@@ -27,6 +27,7 @@ const mocks = vi.hoisted(() => ({
glassPreset: 'natural',
glassQuality: 'css',
glassReflectionStrength: 50,
glassTransmissionStrength: 50,
glassTranslationStrength: 50,
glassTransparencyStrength: 50,
},
@@ -57,6 +58,7 @@ describe('GlassSettingsDialog', () => {
mocks.settings.value.glassPreset = 'natural'
mocks.settings.value.glassQuality = 'css'
mocks.settings.value.glassReflectionStrength = 50
mocks.settings.value.glassTransmissionStrength = 50
mocks.settings.value.glassTranslationStrength = 50
mocks.settings.value.glassTransparencyStrength = 50
})
@@ -76,7 +78,7 @@ describe('GlassSettingsDialog', () => {
})
const sliders = wrapper.findAll('.slider-stub')
expect(sliders).toHaveLength(2)
expect(sliders).toHaveLength(3)
expect(sliders[0].attributes('data-disabled')).toBe('undefined')
expect(sliders[1].attributes('data-disabled')).toBe('undefined')
await wrapper.setProps({ modelValue: false })
@@ -91,6 +93,7 @@ describe('GlassSettingsDialog', () => {
mocks.settings.value.glassDeformationStrength = 65
mocks.settings.value.glassFlowStrength = 61
mocks.settings.value.glassReflectionStrength = 58
mocks.settings.value.glassTransmissionStrength = 57
mocks.settings.value.glassTranslationStrength = 57
mocks.settings.value.glassTransparencyStrength = 55
const wrapper = shallowMount(GlassSettingsDialog, {
@@ -119,7 +122,8 @@ describe('GlassSettingsDialog', () => {
glassDeformationStrength: 82,
glassFlowStrength: 80,
glassPreset: 'liquid',
glassReflectionStrength: 44,
glassReflectionStrength: 31,
glassTransmissionStrength: 58,
glassTranslationStrength: 54,
glassTransparencyStrength: 46,
})
@@ -141,9 +145,9 @@ describe('GlassSettingsDialog', () => {
})
const sliders = wrapper.findAll('.slider-stub')
expect(sliders).toHaveLength(2)
expect(sliders[1].attributes('data-disabled')).toBe('undefined')
await sliders[1].setValue('86')
expect(sliders).toHaveLength(3)
expect(sliders[2].attributes('data-disabled')).toBe('undefined')
await sliders[2].setValue('86')
expect(mocks.previewGlassSettings).toHaveBeenCalledWith({ glassReflectionStrength: 86 })
})
@@ -197,7 +201,7 @@ describe('GlassSettingsDialog', () => {
expect(preset.attributes('data-model-value')).toBe('glide')
})
it('normalizes and previews all five independent slider values', async () => {
it('normalizes and previews all six independent slider values', async () => {
mocks.settings.value.glassQuality = 'balanced'
const wrapper = shallowMount(GlassSettingsDialog, {
global: {
@@ -213,20 +217,22 @@ describe('GlassSettingsDialog', () => {
})
const sliders = wrapper.findAll('.slider-stub')
expect(sliders).toHaveLength(5)
expect(sliders).toHaveLength(6)
expect(sliders.every(slider => slider.attributes('data-disabled') !== 'true')).toBe(true)
await sliders[0].setValue('91.4')
await sliders[1].setValue('73.6')
await sliders[2].setValue('84.2')
await sliders[3].setValue('68.7')
await sliders[4].setValue('62.2')
await sliders[1].setValue('77.5')
await sliders[2].setValue('73.6')
await sliders[3].setValue('84.2')
await sliders[4].setValue('68.7')
await sliders[5].setValue('62.2')
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(1, { glassTransparencyStrength: 91 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(2, { glassReflectionStrength: 74 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(3, { glassTranslationStrength: 84 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(4, { glassDeformationStrength: 69 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(5, { glassFlowStrength: 62 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(2, { glassTransmissionStrength: 78 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(3, { glassReflectionStrength: 74 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(4, { glassTranslationStrength: 84 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(5, { glassDeformationStrength: 69 })
expect(mocks.previewGlassSettings).toHaveBeenNthCalledWith(6, { glassFlowStrength: 62 })
expect(mocks.commitGlassPreview).not.toHaveBeenCalled()
})
@@ -246,9 +252,10 @@ describe('GlassSettingsDialog', () => {
})
const sliders = wrapper.findAll('.slider-stub')
expect(sliders).toHaveLength(5)
expect(sliders).toHaveLength(6)
expect(sliders.map(slider => slider.attributes('aria-label'))).toEqual([
'theme.glassTransparencyStrength',
'theme.glassTransmissionStrength',
'theme.glassReflectionStrength',
'theme.glassTranslationStrength',
'theme.glassDeformationStrength',
@@ -20,6 +20,8 @@ const props = defineProps<{
reflectionStrength: number
/** 用户选择的真实壁纸可见度。 */
transparencyStrength: number
/** 玻璃内部壁纸采样的透射亮度;不会改变外层壁纸的曝光合同。 */
transmissionStrength: number
/** 用户选择的共享壁纸采样平移强度。 */
translationStrength: number
/** 路由变化标识,用于在页面内容稳定后重新发现高价值表面。 */
@@ -34,6 +36,12 @@ const props = defineProps<{
wallpaperUrl: string
/** 切换期保留的上一张壁纸;空值表示当前没有交叉淡化。 */
previousWallpaperUrl: string
/** 下一张同源壁纸;两个 context 均完成上传后才允许外层提交切换。 */
pendingWallpaperUrl?: string
}>()
const emit = defineEmits<{
/** fixed 与 scroll renderer 均已准备同一张待切换纹理。 */
wallpaperPrepared: [url: string]
}>()
const fixedCanvas = ref<HTMLCanvasElement | null>(null)
@@ -49,6 +57,7 @@ const fixedRenderer = useGlassOpticalRenderer({
quality: () => props.quality,
reflectionStrength: () => props.reflectionStrength,
transparencyStrength: () => props.transparencyStrength,
transmissionStrength: () => props.transmissionStrength,
translationStrength: () => props.translationStrength,
routeKey: () => props.routeKey,
tintColor: () => props.tintColor,
@@ -56,6 +65,7 @@ const fixedRenderer = useGlassOpticalRenderer({
transitionStartedAt: () => props.transitionStartedAt,
wallpaperUrl: () => props.wallpaperUrl,
previousWallpaperUrl: () => props.previousWallpaperUrl,
pendingWallpaperUrl: () => props.pendingWallpaperUrl ?? '',
surfaceSpace: 'fixed',
syncDocumentState: false,
})
@@ -69,6 +79,7 @@ const scrollRenderer = useGlassOpticalRenderer({
quality: () => props.quality,
reflectionStrength: () => props.reflectionStrength,
transparencyStrength: () => props.transparencyStrength,
transmissionStrength: () => props.transmissionStrength,
translationStrength: () => props.translationStrength,
routeKey: () => props.routeKey,
tintColor: () => props.tintColor,
@@ -76,6 +87,7 @@ const scrollRenderer = useGlassOpticalRenderer({
transitionStartedAt: () => props.transitionStartedAt,
wallpaperUrl: () => props.wallpaperUrl,
previousWallpaperUrl: () => props.previousWallpaperUrl,
pendingWallpaperUrl: () => props.pendingWallpaperUrl ?? '',
surfaceSpace: 'scroll',
syncDocumentState: false,
})
@@ -94,6 +106,13 @@ watchEffect(() => {
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')
})
+3 -1
View File
@@ -12,7 +12,7 @@ import {
import { usePWA } from '@/composables/usePWA'
import { useI18n } from 'vue-i18n'
import { useTheme } from 'vuetify'
import { GLASS_OPTICAL_STRENGTH_DEFAULT } from '@/utils/glassOptics'
import { GLASS_OPTICAL_STRENGTH_DEFAULT, getGlassOpticalPresetParameters } from '@/utils/glassOptics'
const emit = defineEmits<{
'close': []
@@ -38,6 +38,7 @@ const { appMode } = usePWA()
const { t } = useI18n()
const { global: globalTheme } = useTheme()
const defaultPrimaryColor = themeCustomizerPrimaryColors[0].value
const defaultGlassTransmissionStrength = getGlassOpticalPresetParameters('clear', 'balanced', 'natural').transmission
// 将主题定制器打开状态同步到根节点,供全局悬浮按钮避让右侧面板。
function syncThemeCustomizerOpenState(isOpen: boolean) {
@@ -145,6 +146,7 @@ const hasAppModeCustomization = computed(() => {
settings.value.glassFlowStrength !== GLASS_OPTICAL_STRENGTH_DEFAULT ||
settings.value.glassQuality !== 'css' ||
settings.value.glassReflectionStrength !== GLASS_OPTICAL_STRENGTH_DEFAULT ||
settings.value.glassTransmissionStrength !== defaultGlassTransmissionStrength ||
settings.value.glassTranslationStrength !== GLASS_OPTICAL_STRENGTH_DEFAULT ||
settings.value.glassTransparencyStrength !== GLASS_OPTICAL_STRENGTH_DEFAULT ||
settings.value.radius !== 'default' ||
@@ -40,6 +40,7 @@ describe('GlassOpticalLayer', () => {
tintColor: '#8D51F9',
transitionDuration: 1500,
transitionStartedAt: 0,
transmissionStrength: 84,
translationStrength: 50,
transparencyStrength: 50,
wallpaperUrl: '/wallpaper.jpg',