🐛 fix(window): 修复外接显示器恢复后字体模糊

- 恢复策略:Windows 最小化恢复时不再依赖 viewport drift 才触发修复
- 渲染刷新:普通窗口执行 1px 尺寸 nudge,强制 WebView2/DWM 重建渲染 surface
- 体验保护:最大化窗口继续保留 zoom reset + resize,避免可见重复最大化动画
- 测试覆盖:补充 restore 无 drift 场景与自动修复路径断言
Refs #495
This commit is contained in:
Syngnat
2026-06-12 15:58:30 +08:00
parent f5ee61f589
commit 77a306beb2
3 changed files with 11 additions and 6 deletions

View File

@@ -200,6 +200,9 @@ describe('tool center menu entries', () => {
expect(appSource).toContain('const shouldResetWebViewZoom = shouldResetWebViewZoomForScaleFix(reason, hasViewportScaleDrift);');
expect(appSource).toContain('if (shouldResetWebViewZoom && !isMaximised)');
expect(appSource).toContain('const res = await (window as any).go?.app?.App?.ResetWebViewZoom?.();');
expect(appSource).toContain('if (!shouldApplyWindowsScaleFix(reason, hasViewportScaleDrift))');
expect(appSource).toContain('const nudgedWidth = getWindowsScaleFixNudgedWidth(width);');
expect(appSource).toContain('WindowSetSize(nudgedWidth, height);');
expect(appSource).toContain('该异常不一定表现为 viewport ratio drift');
});

View File

@@ -18,9 +18,11 @@ describe('windowStateUi', () => {
expect(shouldApplyWindowsScaleFix('ratio-change', true)).toBe(true);
});
it('applies the Windows scale fix when a minimized taskbar window is restored with viewport drift', () => {
it('applies the Windows scale fix whenever a minimized taskbar window is restored', () => {
expect(shouldApplyWindowsScaleFix('restore', true)).toBe(true);
expect(shouldApplyWindowsScaleFix('restore', false)).toBe(false);
// 外接显示器恢复后的 WebView2/DWM backing surface 可能被旧 DPI 缩放,
// 但不一定表现为 viewport ratio driftrestore 仍要触发 1px 轻量重绘。
expect(shouldApplyWindowsScaleFix('restore', false)).toBe(true);
// 关键restore 场景刻意不再触发 maximised 窗口的 toggle —— Unmaximise → Maximise 在
// 任务栏恢复的真实交互里会被用户肉眼看见为"重复最大化"动画,比偶发字体变大更糟。
// 这是 9848b8b2 已有的取舍,禁止再次被"修复"成 true。

View File

@@ -6,15 +6,15 @@ export type TitleBarToggleIconKey = 'maximize' | 'restore';
export const shouldApplyWindowsScaleFix = (
reason: WindowScaleFixReason,
hasViewportScaleDrift: boolean,
): boolean => (reason === 'ratio-change' || reason === 'restore') && hasViewportScaleDrift;
): boolean => reason === 'restore' || (reason === 'ratio-change' && hasViewportScaleDrift);
// 关于 restore 场景为何刻意不走 toggle见 9848b8b2
// maximised 窗口在 Windows 上无法通过 SetSize nudge 修复 viewport driftOS 拒绝 resize
// 唯一能让 WebView2 重新计算缩放的办法是 Unmaximise → Maximise但在任务栏图标点击恢复的
// 真实场景下,用户会肉眼看到窗口"被弹两次"的重复最大化动画——比偶发字体变大更糟。
// 取舍restore 时只 dispatch resize 让 React 重算布局,宁可字体保持当前缩放,
// 也不要可见的重复最大化抖动。ratio-changeDPR 变化,例如把窗口拖到另一块显示器)则
// 允许 toggle因为那种场景下用户预期会有视觉过渡。
// 取舍restore 时普通窗口走 1px SetSize nudge 迫使 WebView2/DWM 重新分配 backing surface
// maximised 窗口只做 WebView2 zoom reset + resize避免可见的重复最大化抖动。
// ratio-changeDPR 变化,例如把窗口拖到另一块显示器)则允许 toggle因为那种场景下用户预期会有视觉过渡。
export const shouldToggleMaximisedWindowForScaleFix = (
reason: WindowScaleFixReason,
hasViewportScaleDrift: boolean,