fix(hud): preserve user-dragged position across recording state changes

The HUD overlay snaps back to the centered default position whenever
recording starts because the idle-to-recording UI swap triggers a
resize, and applyHudOverlayBounds() always recomputes a centered
location. This is disruptive when the user has intentionally moved
the bar out of the way before a timed recording.

Remember the position after a drag ends and reuse it for subsequent
bounds updates, clamped to the current work area. The position resets
on app restart or when displays change so the bar cannot get stranded
off-screen.
This commit is contained in:
Andrew Foster
2026-04-11 12:10:19 -05:00
parent d6f1225e45
commit a78f7d45d4
+49 -1
View File
@@ -210,7 +210,25 @@ function applyHudOverlayBounds(expanded: boolean) {
hudOverlayExpanded = expanded;
hudOverlayWindow.setBounds(getHudOverlayBounds(expanded), false);
const computed = getHudOverlayBounds(expanded);
if (hudUserPosition) {
// Resize in-place at the user's dragged position, clamped so the
// window stays fully within the current display's work area.
const { workArea } = getHudOverlayDisplay();
const x = Math.max(
workArea.x,
Math.min(hudUserPosition.x, workArea.x + workArea.width - computed.width),
);
const y = Math.max(
workArea.y,
Math.min(hudUserPosition.y, workArea.y + workArea.height - computed.height),
);
hudOverlayWindow.setBounds({ x, y, width: computed.width, height: computed.height }, false);
} else {
hudOverlayWindow.setBounds(computed, false);
}
positionUpdateToastWindow();
if (!hudOverlayWindow.isVisible()) {
return;
@@ -272,6 +290,10 @@ ipcMain.on("hud-overlay-set-ignore-mouse", (_event, ignore: boolean) => {
}
});
// When the user drags the HUD, remember their chosen position so that
// subsequent size changes (e.g. idle → recording UI swap) resize in-place
// instead of snapping back to the default centered location.
let hudUserPosition: { x: number; y: number } | null = null;
let hudDragOffset: { x: number; y: number } | null = null;
let hudDragLastCursor: { x: number; y: number } | null = null;
let hudDragFixedSize: { width: number; height: number } | null = null;
@@ -304,6 +326,9 @@ ipcMain.on("hud-overlay-drag", (_event, phase: string, screenX: number, screenY:
false,
);
} else if (phase === "end") {
const finalBounds = hudOverlayWindow.getBounds();
hudUserPosition = { x: finalBounds.x, y: finalBounds.y };
hudDragOffset = null;
hudDragLastCursor = null;
hudDragFixedSize = null;
@@ -477,6 +502,29 @@ export function createHudOverlayWindow(): BrowserWindow {
}
});
// Reset the user's saved HUD position when displays change so the bar
// doesn't end up stranded off-screen after a monitor is disconnected.
const screen = getScreen();
screen.on("display-removed", () => {
hudUserPosition = null;
});
screen.on("display-metrics-changed", () => {
if (hudUserPosition) {
const displays = screen.getAllDisplays();
const onScreen = displays.some(
(d) =>
hudUserPosition!.x >= d.workArea.x &&
hudUserPosition!.x < d.workArea.x + d.workArea.width &&
hudUserPosition!.y >= d.workArea.y &&
hudUserPosition!.y < d.workArea.y + d.workArea.height,
);
if (!onScreen) {
hudUserPosition = null;
applyHudOverlayBounds(hudOverlayExpanded);
}
}
});
if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL + "?windowType=hud-overlay");
} else {