🐛 fix(window): 修复 Windows 恢复窗口后字体缩放异常

- 记录最小化和隐藏状态以识别任务栏恢复场景

- 恢复窗口时使用 restore 缩放修复路径校正 viewport drift

- 增加任务栏恢复窗口缩放逻辑测试
This commit is contained in:
Syngnat
2026-05-12 21:47:24 +08:00
parent 10a695ba0f
commit 0fea730908
3 changed files with 53 additions and 7 deletions

View File

@@ -73,7 +73,7 @@ import {
splitConflictsByContext,
type ConflictInfo,
} from './utils/shortcuts';
import { resolveTitleBarToggleIconKey, resolveWindowsScaleCheckDelayMs, shouldApplyWindowsScaleFix, shouldToggleMaximisedWindowForScaleFix, type WindowsScaleCheckTrigger } from './utils/windowStateUi';
import { resolveTitleBarToggleIconKey, resolveWindowsScaleCheckDelayMs, shouldApplyWindowsScaleFix, shouldToggleMaximisedWindowForScaleFix, type WindowScaleFixReason, type WindowsScaleCheckTrigger } from './utils/windowStateUi';
import { resolveVisibleStartupWindowBounds } from './utils/windowRestoreBounds';
import {
SIDEBAR_UTILITY_ITEM_KEYS,
@@ -635,10 +635,12 @@ function App() {
let lastFixAt = 0;
let activationTimer: number | null = null;
let resizeTimer: number | null = null;
let minimisedSeen = false;
let hiddenSeen = document.visibilityState === 'hidden';
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
const fixWindowScaleIfNeeded = async (reason: 'activation' | 'ratio-change') => {
const fixWindowScaleIfNeeded = async (reason: WindowScaleFixReason) => {
if (cancelled || inFlight) return;
const now = Date.now();
if (now - lastFixAt < 700) return;
@@ -713,6 +715,22 @@ function App() {
}
};
const rememberMinimisedState = async (): Promise<boolean> => {
if (cancelled) return false;
const isMinimised = await WindowIsMinimised().catch(() => false);
if (isMinimised) {
minimisedSeen = true;
}
return isMinimised;
};
const rememberMinimisedStateSoon = () => {
window.setTimeout(() => {
if (cancelled) return;
void rememberMinimisedState();
}, 120);
};
const checkDevicePixelRatio = () => {
if (cancelled) return;
const currentRatio = Number(window.devicePixelRatio) || 1;
@@ -746,10 +764,16 @@ function App() {
if (activationTimer !== null) {
window.clearTimeout(activationTimer);
}
activationTimer = window.setTimeout(() => {
activationTimer = window.setTimeout(async () => {
activationTimer = null;
if (cancelled) return;
void fixWindowScaleIfNeeded('activation');
if (await rememberMinimisedState()) {
return;
}
const reason: WindowScaleFixReason = (minimisedSeen || hiddenSeen) ? 'restore' : 'activation';
minimisedSeen = false;
hiddenSeen = false;
void fixWindowScaleIfNeeded(reason);
}, 80);
};
@@ -759,9 +783,19 @@ function App() {
scheduleActivationFix();
};
const handleWindowBlur = () => {
if (cancelled) return;
if (document.visibilityState === 'hidden') {
hiddenSeen = true;
}
rememberMinimisedStateSoon();
};
const handleVisibilityChange = () => {
if (cancelled) return;
if (document.visibilityState !== 'visible') {
hiddenSeen = true;
rememberMinimisedStateSoon();
return;
}
scheduleDevicePixelRatioCheck('visibilitychange');
@@ -775,12 +809,17 @@ function App() {
};
const handleWindowResize = () => {
rememberMinimisedStateSoon();
scheduleDevicePixelRatioCheck('resize');
};
const pollTimer = window.setInterval(checkDevicePixelRatio, 900);
const pollTimer = window.setInterval(() => {
void rememberMinimisedState();
checkDevicePixelRatio();
}, 900);
window.addEventListener('resize', handleWindowResize);
window.addEventListener('focus', handleWindowFocus);
window.addEventListener('blur', handleWindowBlur);
window.addEventListener('pageshow', handlePageShow);
document.addEventListener('visibilitychange', handleVisibilityChange);
@@ -795,6 +834,7 @@ function App() {
window.clearInterval(pollTimer);
window.removeEventListener('resize', handleWindowResize);
window.removeEventListener('focus', handleWindowFocus);
window.removeEventListener('blur', handleWindowBlur);
window.removeEventListener('pageshow', handlePageShow);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};

View File

@@ -17,6 +17,12 @@ 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', () => {
expect(shouldApplyWindowsScaleFix('restore', true)).toBe(true);
expect(shouldApplyWindowsScaleFix('restore', false)).toBe(false);
expect(shouldToggleMaximisedWindowForScaleFix('restore', true)).toBe(true);
});
it('debounces resize-triggered Windows scale checks until window transitions settle', () => {
expect(resolveWindowsScaleCheckDelayMs('resize')).toBeGreaterThan(0);
expect(resolveWindowsScaleCheckDelayMs('focus')).toBe(0);

View File

@@ -1,12 +1,12 @@
export type WindowVisualState = 'normal' | 'maximized' | 'fullscreen';
export type WindowScaleFixReason = 'activation' | 'ratio-change';
export type WindowScaleFixReason = 'activation' | 'ratio-change' | 'restore';
export type WindowsScaleCheckTrigger = 'focus' | 'pageshow' | 'poll' | 'resize' | 'visibilitychange';
export type TitleBarToggleIconKey = 'maximize' | 'restore';
export const shouldApplyWindowsScaleFix = (
reason: WindowScaleFixReason,
hasViewportScaleDrift: boolean,
): boolean => reason === 'ratio-change' && hasViewportScaleDrift;
): boolean => (reason === 'ratio-change' || reason === 'restore') && hasViewportScaleDrift;
export const shouldToggleMaximisedWindowForScaleFix = shouldApplyWindowsScaleFix;