fix(electrobun): JS-based window resize replaces GTK begin_resize_drag

GTK begin_resize_drag loses grip when cursor moves inward past the 6px
handle zone. Replaced with document-level mousemove/mouseup listeners
that compute delta from initial frame and call setPosition+setSize.

- clearMinSize RPC clears WebView min-size before resize starts
- Cursor locks to resize direction during drag (body.style.cursor)
- user-select disabled during drag to prevent text selection
- Frame captured async before resize starts (no race condition)
This commit is contained in:
Hibryda 2026-03-25 13:09:15 +01:00
parent 290ae8ef86
commit e6635e436c
2 changed files with 63 additions and 11 deletions

View file

@ -142,13 +142,18 @@ const rpc = BrowserView.defineRPC<PtyRPCSchema>({
"window.setPosition": ({ x, y }: { x: number; y: number }) => { try { mainWindow.setPosition(x, y); return { ok: true }; } catch { return { ok: false }; } },
"window.setFrame": ({ x, y, width, height }: { x: number; y: number; width: number; height: number }) => {
try {
// Use separate setPosition + setSize — setFrame's C implementation
// may not resize on GTK with titleBarStyle:"hidden"
mainWindow.setPosition(x, y);
mainWindow.setSize(width, height);
return { ok: true };
} catch (err) { console.error("[window.setFrame]", err); return { ok: false }; }
},
"window.clearMinSize": () => {
try {
const { ensureResizable } = require("./gtk-window.ts");
ensureResizable((mainWindow as any).ptr);
return { ok: true };
} catch (err) { console.error("[window.clearMinSize]", err); return { ok: false }; }
},
},
messages: {},
},

View file

@ -104,18 +104,61 @@
e.preventDefault();
}
// ── Window resize — delegates to GTK window manager ────────
function onResizeStart(e: MouseEvent, edge: string) {
appRpc.request['window.beginResize']({
edge,
button: e.button + 1,
rootX: e.screenX,
rootY: e.screenY,
}).catch(() => {});
// ── Window resize — JS-based with GTK min-size clear ────────
let resizeEdge: string | null = null;
let resizeStartX = 0;
let resizeStartY = 0;
let resizeFrame = { x: 0, y: 0, width: 0, height: 0 };
const CURSOR_MAP: Record<string, string> = {
n: 'n-resize', s: 's-resize', e: 'e-resize', w: 'w-resize',
ne: 'ne-resize', nw: 'nw-resize', se: 'se-resize', sw: 'sw-resize',
};
async function onResizeStart(e: MouseEvent, edge: string) {
// Clear min-size constraints first (enables shrink)
appRpc.request['window.clearMinSize']({}).catch(() => {});
// Capture current frame BEFORE starting resize
try {
const frame = await appRpc.request['window.getFrame']({});
resizeFrame = { x: frame.x, y: frame.y, width: frame.width, height: frame.height };
} catch {
// fallback — use defaults
resizeFrame = { x: 100, y: 100, width: 1400, height: 900 };
}
resizeEdge = edge;
resizeStartX = e.screenX;
resizeStartY = e.screenY;
// Lock cursor during resize
document.body.style.cursor = CURSOR_MAP[edge] || 'default';
document.body.style.userSelect = 'none';
e.preventDefault();
e.stopPropagation();
}
function onResizeMove(e: MouseEvent) {
if (!resizeEdge) return;
const dx = e.screenX - resizeStartX;
const dy = e.screenY - resizeStartY;
let { x, y, width, height } = resizeFrame;
const MIN_W = 400, MIN_H = 300;
if (resizeEdge.includes('e')) width = Math.max(MIN_W, width + dx);
if (resizeEdge.includes('w')) { const nw = Math.max(MIN_W, width - dx); x += width - nw; width = nw; }
if (resizeEdge.includes('s')) height = Math.max(MIN_H, height + dy);
if (resizeEdge.includes('n')) { const nh = Math.max(MIN_H, height - dy); y += height - nh; height = nh; }
appRpc.request['window.setFrame']({ x: Math.round(x), y: Math.round(y), width: Math.round(width), height: Math.round(height) }).catch(() => {});
}
function onResizeEnd() {
if (!resizeEdge) return;
resizeEdge = null;
document.body.style.cursor = '';
document.body.style.userSelect = '';
saveWindowFrame();
}
// ── Window frame persistence (debounced 500ms) ──────────────
let frameSaveTimer: ReturnType<typeof setTimeout> | null = null;
function saveWindowFrame() {
@ -214,7 +257,9 @@
setAgentToastFn(showToast);
setupErrorBoundary();
// No global mousemove/mouseup needed — GTK WM handles drag/resize natively
// JS-based resize needs global mouse listeners
document.addEventListener('mousemove', onResizeMove);
document.addEventListener('mouseup', onResizeEnd);
// Blink + session timers — MUST be in onMount, NOT $effect
// $effect interacts with reactive graph and causes cycles
@ -310,6 +355,8 @@
clearInterval(sessionId);
document.removeEventListener("keydown", handleSearchShortcut);
window.removeEventListener("palette-command", handlePaletteCommand);
document.removeEventListener('mousemove', onResizeMove);
document.removeEventListener('mouseup', onResizeEnd);
};
});
</script>