Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions apps/overlay/electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions apps/overlay/electron/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions apps/overlay/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -870,6 +887,15 @@ <h3>Controller Overlay</h3>
<button data-preset="top">Top</button>
</div>

<div class="setting-row">
<label title="Overlay window size in pixels. Updates live as you resize the window, and editing a value resizes the window to match. The last size is remembered between launches.">Window Size</label>
<span class="window-size-fields">
<input type="number" id="window-width" min="200" max="8000" step="1" autocomplete="off" title="Width (px)">
<span class="window-size-x">×</span>
<input type="number" id="window-height" min="200" max="8000" step="1" autocomplete="off" title="Height (px)">
</span>
</div>

<div class="setting-row">
<label title="Float the triggers, bumpers & back paddles clear of the body so they're visible at any angle">Float Controls</label>
<input type="checkbox" id="float-controls" checked autocomplete="off">
Expand Down
43 changes: 43 additions & 0 deletions apps/overlay/src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down