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 @@