mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-22 08:50:17 +08:00
@@ -20,6 +20,7 @@ import { buildOverlayWorkbenchTheme } from './utils/overlayWorkbenchTheme';
|
||||
import { getConnectionWorkbenchState } from './utils/startupReadiness';
|
||||
import { createGlobalProxyDraft, toSaveGlobalProxyInput } from './utils/globalProxyDraft';
|
||||
import { LEGACY_PERSIST_KEY, readLegacyPersistedSecrets, stripLegacyPersistedSecrets } from './utils/legacyConnectionStorage';
|
||||
import { getWindowsScaleFixNudgedWidth, hasWindowsViewportScaleDrift } from './utils/windowsScaleFix';
|
||||
import {
|
||||
SHORTCUT_ACTION_META,
|
||||
SHORTCUT_ACTION_ORDER,
|
||||
@@ -534,7 +535,7 @@ function App() {
|
||||
|
||||
const wait = (ms: number) => new Promise<void>((resolve) => window.setTimeout(resolve, ms));
|
||||
|
||||
const fixWindowScaleIfNeeded = async () => {
|
||||
const fixWindowScaleIfNeeded = async (reason: 'activation' | 'ratio-change') => {
|
||||
if (cancelled || inFlight) return;
|
||||
const now = Date.now();
|
||||
if (now - lastFixAt < 700) return;
|
||||
@@ -545,8 +546,8 @@ function App() {
|
||||
WindowIsMaximised().catch(() => false),
|
||||
]);
|
||||
|
||||
// 避免在全屏/最大化状态下强制改尺寸;这两种状态通常能自行保持 DPI 同步。
|
||||
if (isFullscreen || isMaximised) {
|
||||
// 全屏状态下只广播 resize,避免破坏用户的全屏上下文。
|
||||
if (isFullscreen) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
lastFixAt = Date.now();
|
||||
return;
|
||||
@@ -555,13 +556,46 @@ function App() {
|
||||
const size = await WindowGetSize().catch(() => null);
|
||||
const width = Math.trunc(Number(size?.w || 0));
|
||||
const height = Math.trunc(Number(size?.h || 0));
|
||||
const hasViewportScaleDrift = hasWindowsViewportScaleDrift({
|
||||
windowWidth: width,
|
||||
innerWidth: window.innerWidth,
|
||||
devicePixelRatio: Number(window.devicePixelRatio) || 1,
|
||||
visualViewportScale: window.visualViewport?.scale,
|
||||
});
|
||||
|
||||
if (isMaximised) {
|
||||
if (reason !== 'ratio-change' && !hasViewportScaleDrift) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
lastFixAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
WindowToggleMaximise();
|
||||
await wait(48);
|
||||
WindowToggleMaximise();
|
||||
await wait(64);
|
||||
} catch (e) {
|
||||
console.warn("Wails Window maximise toggle unavailable in fixWindowScaleIfNeeded", e);
|
||||
}
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
lastFixAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
if (width <= 0 || height <= 0) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
lastFixAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
const nudgedWidth = width > 480 ? width - 1 : width + 1;
|
||||
if (reason !== 'ratio-change' && !hasViewportScaleDrift) {
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
lastFixAt = Date.now();
|
||||
return;
|
||||
}
|
||||
|
||||
const nudgedWidth = getWindowsScaleFixNudgedWidth(width);
|
||||
try {
|
||||
WindowSetSize(nudgedWidth, height);
|
||||
await wait(28);
|
||||
@@ -583,7 +617,7 @@ function App() {
|
||||
return;
|
||||
}
|
||||
lastRatio = currentRatio;
|
||||
void fixWindowScaleIfNeeded();
|
||||
void fixWindowScaleIfNeeded('ratio-change');
|
||||
};
|
||||
|
||||
const scheduleActivationFix = () => {
|
||||
@@ -594,7 +628,7 @@ function App() {
|
||||
activationTimer = window.setTimeout(() => {
|
||||
activationTimer = null;
|
||||
if (cancelled) return;
|
||||
void fixWindowScaleIfNeeded();
|
||||
void fixWindowScaleIfNeeded('activation');
|
||||
}, 80);
|
||||
};
|
||||
|
||||
|
||||
45
frontend/src/utils/windowsScaleFix.test.ts
Normal file
45
frontend/src/utils/windowsScaleFix.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
computeWindowsViewportScaleRatio,
|
||||
getWindowsScaleFixNudgedWidth,
|
||||
hasWindowsViewportScaleDrift,
|
||||
} from './windowsScaleFix';
|
||||
|
||||
describe('windowsScaleFix', () => {
|
||||
it('treats matching window and viewport metrics as stable', () => {
|
||||
const ratio = computeWindowsViewportScaleRatio({
|
||||
windowWidth: 1920,
|
||||
innerWidth: 1280,
|
||||
devicePixelRatio: 1.5,
|
||||
});
|
||||
|
||||
expect(ratio).toBeCloseTo(1, 5);
|
||||
expect(hasWindowsViewportScaleDrift({
|
||||
windowWidth: 1920,
|
||||
innerWidth: 1280,
|
||||
devicePixelRatio: 1.5,
|
||||
})).toBe(false);
|
||||
});
|
||||
|
||||
it('detects zoom drift from viewport width mismatch', () => {
|
||||
expect(hasWindowsViewportScaleDrift({
|
||||
windowWidth: 1920,
|
||||
innerWidth: 1100,
|
||||
devicePixelRatio: 1.5,
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it('detects zoom drift from visual viewport scale', () => {
|
||||
expect(hasWindowsViewportScaleDrift({
|
||||
windowWidth: 1600,
|
||||
innerWidth: 1600,
|
||||
devicePixelRatio: 1,
|
||||
visualViewportScale: 1.12,
|
||||
})).toBe(true);
|
||||
});
|
||||
|
||||
it('returns a one-pixel nudge width for normal windows', () => {
|
||||
expect(getWindowsScaleFixNudgedWidth(960)).toBe(959);
|
||||
expect(getWindowsScaleFixNudgedWidth(420)).toBe(421);
|
||||
});
|
||||
});
|
||||
46
frontend/src/utils/windowsScaleFix.ts
Normal file
46
frontend/src/utils/windowsScaleFix.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
type WindowsViewportScaleInput = {
|
||||
windowWidth: number;
|
||||
innerWidth: number;
|
||||
devicePixelRatio: number;
|
||||
visualViewportScale?: number | null;
|
||||
};
|
||||
|
||||
export const computeWindowsViewportScaleRatio = ({
|
||||
windowWidth,
|
||||
innerWidth,
|
||||
devicePixelRatio,
|
||||
}: WindowsViewportScaleInput): number => {
|
||||
const normalizedWindowWidth = Number(windowWidth);
|
||||
const normalizedInnerWidth = Number(innerWidth);
|
||||
const normalizedDevicePixelRatio = Number(devicePixelRatio);
|
||||
if (
|
||||
!Number.isFinite(normalizedWindowWidth) || normalizedWindowWidth <= 0 ||
|
||||
!Number.isFinite(normalizedInnerWidth) || normalizedInnerWidth <= 0 ||
|
||||
!Number.isFinite(normalizedDevicePixelRatio) || normalizedDevicePixelRatio <= 0
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return (normalizedWindowWidth / normalizedDevicePixelRatio) / normalizedInnerWidth;
|
||||
};
|
||||
|
||||
export const hasWindowsViewportScaleDrift = (
|
||||
metrics: WindowsViewportScaleInput,
|
||||
tolerance = 0.08,
|
||||
): boolean => {
|
||||
const normalizedTolerance = Math.max(0.01, Number(tolerance) || 0.08);
|
||||
const visualViewportScale = Number(metrics.visualViewportScale);
|
||||
if (Number.isFinite(visualViewportScale) && Math.abs(visualViewportScale - 1) > normalizedTolerance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const viewportScaleRatio = computeWindowsViewportScaleRatio(metrics);
|
||||
return Math.abs(viewportScaleRatio - 1) > normalizedTolerance;
|
||||
};
|
||||
|
||||
export const getWindowsScaleFixNudgedWidth = (width: number): number => {
|
||||
const normalizedWidth = Math.trunc(Number(width) || 0);
|
||||
if (normalizedWidth <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return normalizedWidth > 480 ? normalizedWidth - 1 : normalizedWidth + 1;
|
||||
};
|
||||
Reference in New Issue
Block a user