返回 DeepSeek-Reasonix
useWailsResizeFix.ts
根目录 / desktop / frontend / src / lib / useWailsResizeFix.ts
1 import { useEffect } from "react";
2
3 const RESIZE_CURSORS = new Set([
4 "e-resize",
5 "n-resize",
6 "ne-resize",
7 "nw-resize",
8 "s-resize",
9 "se-resize",
10 "sw-resize",
11 "w-resize",
12 ]);
13
14 /**
15 * WailsWailsFlags mirrors the `window.wails.flags` object injected by the
16 * Wails v2 runtime (see internal/frontend/runtime/desktop/main.js).
17 */
18 interface WailsFlags {
19 enableResize: boolean;
20 resizeEdge: string | undefined;
21 borderThickness: number;
22 defaultCursor: string | null;
23 cssDragProperty: string;
24 cssDragValue: string;
25 cssDropProperty: string;
26 cssDropValue: string;
27 shouldDrag: boolean;
28 deferDragToMouseMove: boolean;
29 disableScrollbarDrag: boolean;
30 disableDefaultContextMenu: boolean;
31 enableWailsDragAndDrop: boolean;
32 }
33
34 interface WailsWindow {
35 wails: {
36 flags: WailsFlags;
37 Callback: (msg: string) => void;
38 EventsNotify: (msg: string) => void;
39 };
40 }
41
42 declare global {
43 interface Window {
44 wails?: WailsWindow["wails"];
45 }
46 }
47
48 /**
49 * useWailsResizeFix
50 *
51 * Workaround for a Wails v2.12 frameless-window resize detection bug where
52 * `window.outerWidth/outerHeight` (device-independent pixels) is compared
53 * with `e.clientX/e.clientY` (CSS pixels). When WebView2 ZoomFactor ≠ 1
54 * these coordinate spaces diverge, causing the right/bottom resize zone to
55 * extend far inward or disappear entirely.
56 *
57 * This hook disables the built-in mousemove handler (which uses the wrong
58 * coordinate space) and replaces it with one that uses `window.innerWidth`
59 * and `window.innerHeight` — both in CSS pixels, matching `e.clientX/Y`.
60 *
61 * The Wails mousedown handler still works because it only reads
62 * `window.wails.flags.resizeEdge`, which we continue to set here.
63 *
64 * --- Maximised-window guard ---
65 *
66 * Maximised state cannot be inferred safely from viewport dimensions: a
67 * manually sized or FancyZones-managed window can also fill the work area.
68 * Instead, this hook consumes the native maximise state shared with the Windows
69 * titlebar controls. Mousemove stays synchronous and never performs IPC.
70 *
71 * When maximised, edge detection is skipped and any stale resize cursor is
72 * replaced with an explicit default cursor. This also covers startup restores
73 * where the native cursor is already stuck but Wails' `resizeEdge` flag was
74 * never populated.
75 *
76 * Upstream fix: https://github.com/wailsapp/wails/issues/4590 (Wails v3
77 * sidestepped by clamping zoom ≥ 1.0). Once Wails v2 ships a proper fix
78 * this hook can be deleted.
79 *
80 * @example
81 * // In App.tsx or any component mounted for the app's lifetime:
82 * useWailsResizeFix(desktopPlatform === "windows");
83 */
84 export function useWailsResizeFix(enabled: boolean, maximised = false): void {
85 useEffect(() => {
86 if (!enabled) return;
87 const wails = window.wails;
88 if (!wails) return; // not inside a Wails webview → no-op
89
90 const flags = wails.flags;
91 const bt = flags.borderThickness ?? 6;
92 const previousEnableResize = flags.enableResize;
93 const previousResizeEdge = flags.resizeEdge;
94 const previousCursor = document.documentElement.style.cursor;
95
96 // Prefer Wails' remembered cursor. A resize-shaped inline cursor at mount
97 // is stale state, not the application's default.
98 const rememberedCursor = flags.defaultCursor ?? previousCursor;
99 const defaultCursor = RESIZE_CURSORS.has(rememberedCursor) ? "" : rememberedCursor;
100 const restoredCursor = defaultCursor || "default";
101
102 const clearResizeState = () => {
103 flags.resizeEdge = undefined;
104 if (document.documentElement.style.cursor !== restoredCursor) {
105 document.documentElement.style.cursor = restoredCursor;
106 }
107 };
108
109 // Normalise stale startup state immediately, before the first native state
110 // query completes. A normal window will restore the correct edge on its next
111 // mousemove; a maximised window keeps the default cursor.
112 if (maximised || previousResizeEdge !== undefined || RESIZE_CURSORS.has(previousCursor)) {
113 clearResizeState();
114 }
115
116 const onMouseMove = (e: MouseEvent) => {
117 if (maximised) {
118 clearResizeState();
119 return;
120 }
121
122 // Both operands in CSS pixels — the bug fix.
123 const iw = window.innerWidth;
124 const ih = window.innerHeight;
125 const cx = e.clientX;
126 const cy = e.clientY;
127
128 const right = iw - cx < bt;
129 const left = cx < bt;
130 const top = cy < bt;
131 const bottom = ih - cy < bt;
132
133 let edge: string | undefined;
134 if (right && bottom) edge = "se-resize";
135 else if (left && bottom) edge = "sw-resize";
136 else if (right && top) edge = "ne-resize";
137 else if (left && top) edge = "nw-resize";
138 else if (right) edge = "e-resize";
139 else if (left) edge = "w-resize";
140 else if (top) edge = "n-resize";
141 else if (bottom) edge = "s-resize";
142
143 if (edge !== flags.resizeEdge) {
144 flags.resizeEdge = edge;
145 document.documentElement.style.cursor = edge ?? restoredCursor;
146 }
147 };
148
149 // Disable Wails' built-in mousemove handler (the one that uses outerWidth).
150 flags.enableResize = false;
151 window.addEventListener("mousemove", onMouseMove);
152
153 return () => {
154 window.removeEventListener("mousemove", onMouseMove);
155 flags.enableResize = previousEnableResize;
156 flags.resizeEdge = previousResizeEdge;
157 document.documentElement.style.cursor = previousCursor;
158 };
159 }, [enabled, maximised]);
160 }
161
161 lines TYPESCRIPT