From 691ce2eae9a700ef8a9ede4bc52ec8203ec2d7dd Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 19:52:24 +0000 Subject: [PATCH] =?UTF-8?q?Add=20editable=20live=20width=C3=97height=20fie?= =?UTF-8?q?lds=20to=20settings=20(#69=20follow-up)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The window size was already saved/restored, but there was no way to see or set the exact dimensions. Add a "Window Size" row to the settings panel with two numeric inputs that: - show the current overlay window size on open, - update live as the user drag-resizes the window (main broadcasts 'window-size-changed' on every resize), and - resize the window to an exact size when edited (committed on blur/Enter). Main process exposes get-window-size / set-window-size over IPC, with the set path clamped to 200–8000 px so a stray value can't break the window. The renderer block no-ops (and hides the row) when electronAPI is absent, so the web build is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_017NiS2a4jZ877XgftkH1Dd1 --- apps/overlay/electron/main.js | 26 +++++++++++++++++++ apps/overlay/electron/preload.js | 10 ++++++++ apps/overlay/src/index.html | 26 +++++++++++++++++++ apps/overlay/src/js/app.js | 43 ++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) diff --git a/apps/overlay/electron/main.js b/apps/overlay/electron/main.js index 66eb6be..b65d876 100644 --- a/apps/overlay/electron/main.js +++ b/apps/overlay/electron/main.js @@ -186,6 +186,11 @@ function createWindow() { writeWindowState(state); }; mainWindow.on('resize', () => { + // Live-update the settings panel's width×height fields as the user drags. + if (mainWindow && !mainWindow.isDestroyed()) { + const [w, h] = mainWindow.getSize(); + mainWindow.webContents.send('window-size-changed', { width: w, height: h }); + } if (saveWindowSizeTimer) clearTimeout(saveWindowSizeTimer); saveWindowSizeTimer = setTimeout(persistSize, 400); }); @@ -267,6 +272,27 @@ app.whenReady().then(() => { // Handle quit from renderer ipcMain.on('quit-app', () => app.quit()); + // ── Window size readout/editor (issue #69 follow-up) ── + // The settings panel shows the live width×height and lets the user type an + // exact size. getSize is the source of truth; setSize drives the window + // (clamped to sane bounds so a stray keystroke can't shrink it to nothing + // or blow it up off-screen). The matching 'window-size-changed' broadcast + // (see createWindow) keeps the fields in sync when the user drag-resizes. + ipcMain.handle('get-window-size', () => { + if (!mainWindow || mainWindow.isDestroyed()) return null; + const [width, height] = mainWindow.getSize(); + return { width, height }; + }); + ipcMain.on('set-window-size', (_event, { width, height }) => { + if (!mainWindow || mainWindow.isDestroyed()) return; + const w = Math.round(Number(width)); + const h = Math.round(Number(height)); + if (!Number.isFinite(w) || !Number.isFinite(h)) return; + const clampedW = Math.max(200, Math.min(8000, w)); + const clampedH = Math.max(200, Math.min(8000, h)); + mainWindow.setSize(clampedW, clampedH); + }); + // ── Frameless window drag (renderer grabs a non-interactive area) ── // Renderer signals start/move/end; we compute position from the live // cursor so the grab point stays pinned under the pointer regardless of diff --git a/apps/overlay/electron/preload.js b/apps/overlay/electron/preload.js index e11cfe4..fe24fa3 100644 --- a/apps/overlay/electron/preload.js +++ b/apps/overlay/electron/preload.js @@ -5,6 +5,16 @@ contextBridge.exposeInMainWorld('electronAPI', { onToggleSettings: (callback) => ipcRenderer.on('toggle-settings', () => callback()), quit: () => ipcRenderer.send('quit-app'), + // ── Window size readout/editor (settings panel width×height fields) ── + // getWindowSize() → { width, height } current size. + // setWindowSize(w, h) resizes the overlay window (clamped in main). + // onWindowSizeChanged(cb) fires whenever the window is resized (drag or set) + // so the fields track the live size. + getWindowSize: () => ipcRenderer.invoke('get-window-size'), + setWindowSize: (width, height) => ipcRenderer.send('set-window-size', { width, height }), + onWindowSizeChanged: (callback) => + ipcRenderer.on('window-size-changed', (_, size) => callback(size)), + // ── Frameless window drag ── // The renderer detects a grab on a non-interactive area and drives the // window move through the main process (which reads the live cursor for diff --git a/apps/overlay/src/index.html b/apps/overlay/src/index.html index 9c705a6..7fa704c 100644 --- a/apps/overlay/src/index.html +++ b/apps/overlay/src/index.html @@ -143,6 +143,23 @@ accent-color: #3388ff; cursor: pointer; } + + .window-size-fields { display: flex; align-items: center; gap: 4px; } + .window-size-x { color: #888; font-size: 11px; } + .setting-row input[type="number"] { + width: 52px; + background: rgba(255,255,255,0.08); + color: #eee; + border: 1px solid rgba(255,255,255,0.12); + border-radius: 5px; + padding: 4px 6px; + font-size: 11px; + text-align: right; + } + .setting-row input[type="number"]:focus { + outline: none; + border-color: #3388ff; + } .settings-divider { border: none; border-top: 1px solid rgba(255,255,255,0.08); @@ -870,6 +887,15 @@

Controller Overlay

+
+ + + + × + + +
+
diff --git a/apps/overlay/src/js/app.js b/apps/overlay/src/js/app.js index 0275304..63e2f53 100644 --- a/apps/overlay/src/js/app.js +++ b/apps/overlay/src/js/app.js @@ -2131,6 +2131,49 @@ cameraPresetBtns.forEach((btn) => { // view once the overlay exists. selectCameraPreset(selectedCameraPreset); +// ── Window size fields (issue #69 follow-up) ── +// Two numeric inputs in the settings panel mirror the live overlay window +// size: they update as the user drag-resizes the window, and typing a value +// resizes the window to match. Electron-only (the web build has no window to +// resize), so the whole block no-ops when electronAPI is absent. +const windowWidthInput = document.getElementById('window-width'); +const windowHeightInput = document.getElementById('window-height'); +if (windowWidthInput && windowHeightInput && window.electronAPI?.getWindowSize) { + let suppressSizeApply = false; // guard against echo while we set field values + + const setSizeFields = ({ width, height }) => { + suppressSizeApply = true; + // Don't stomp on a field the user is mid-edit in. + if (document.activeElement !== windowWidthInput) windowWidthInput.value = width; + if (document.activeElement !== windowHeightInput) windowHeightInput.value = height; + suppressSizeApply = false; + }; + + // Seed the fields with the current size. + window.electronAPI.getWindowSize().then((size) => { if (size) setSizeFields(size); }); + + // Track live drag-resizes from the main process. + window.electronAPI.onWindowSizeChanged?.((size) => { if (size) setSizeFields(size); }); + + // Push edits to the window. 'change' (commit on blur/Enter) keeps us from + // resizing on every intermediate keystroke; the main process clamps to + // 200–8000 px so an out-of-range value can't break the window. + const applySize = () => { + if (suppressSizeApply) return; + const w = parseInt(windowWidthInput.value, 10); + const h = parseInt(windowHeightInput.value, 10); + if (Number.isFinite(w) && Number.isFinite(h)) { + window.electronAPI.setWindowSize?.(w, h); + } + }; + windowWidthInput.addEventListener('change', applySize); + windowHeightInput.addEventListener('change', applySize); +} else if (windowWidthInput) { + // No window to resize (web build) — hide the row so it isn't a dead control. + const row = windowWidthInput.closest('.setting-row'); + if (row) row.style.display = 'none'; +} + // ── Window display toggles (cosmetic only — never affects functionality) ── const showTitleCheck = document.getElementById('show-title'); const showGyroCheck = document.getElementById('show-gyro');