From 1918189079a066f0fa3fa9d705f2c0d502d85992 Mon Sep 17 00:00:00 2001 From: Side Quest Studios Date: Fri, 24 Jul 2026 22:12:05 -0400 Subject: [PATCH] fix: hide Studio window during recording, let webcam bubble use full screen Two related UX fixes: - The Studio window stayed in front the moment you clicked Record, forcing you to manually switch away from VybeClip before you could even start capturing anything. showOrCreateRecordingControls() now hides it (not closes - the existing restore-on-return logic in createWindow()/focusOrCreateMainWindow() brings it back unchanged). hud-overlay-close restores it again on cancel, but not when a recording successfully hands off to the editor (checked via getExistingEditorWindow()), so the editor stays the sole window in that case. - The floating webcam preview bubble was stuck at the bottom edge of the screen, only draggable a little side to side. Root cause: the HUD overlay window intentionally shrinks to a compact, non-click-through band during active recording (isHudOverlayCanvasActive), so the stop/pause controls stay reliably clickable without depending on hover-based passthrough toggling - but that band is only tall enough for the HUD bar itself, leaving the bubble nowhere to go; it was being visually clipped at the window's real edge, not merely CSS-clipped. The renderer now tells the main process whenever a webcam preview is actually showing (setHudOverlayWebcamPreviewActive), and the window widens to the full work area in that case. This is deliberately kept separate from isHudOverlayCanvasActive() itself, which also gates the monitor-boundary-crossing drag relocation logic and should stay tied to the platform/passthrough check alone. The existing hover-driven mouse-passthrough toggling (already wired to the bubble's own pointer events) continues to keep clicks passing through everywhere except the HUD bar/bubble, independent of the window's size. --- electron/electron-env.d.ts | 1 + electron/main.ts | 27 ++++++++++++++++ electron/preload.ts | 3 ++ electron/windows.ts | 32 ++++++++++++++++--- .../launch/hooks/useWebcamPreviewOverlay.ts | 13 ++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 3a31d9336..553843406 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -226,6 +226,7 @@ interface Window { switchToEditor: () => Promise; openSourceSelector: () => Promise; showRecordingControls: () => Promise<{ success: boolean }>; + setHudOverlayWebcamPreviewActive: (active: boolean) => Promise<{ success: boolean }>; selectSource: (source: ProcessedDesktopSource) => Promise; showSourceHighlight: (source: ProcessedDesktopSource) => Promise<{ success: boolean }>; getSelectedSource: () => Promise; diff --git a/electron/main.ts b/electron/main.ts index ce06c9441..4a3ce3e12 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -52,6 +52,7 @@ import { isHudOverlayMousePassthroughSupported, reassertHudOverlayMousePassthrough as reassertHudOverlayMouseState, setHudOverlayRecordingActive, + setHudOverlayWebcamPreviewActive, showUpdateToastWindow, } from "./windows"; @@ -311,6 +312,16 @@ function showHudOverlayFromTray() { } function showOrCreateRecordingControls() { + // The Studio window has no reason to stay in front once the user is + // about to record - it just sits in the way of whatever they're trying + // to capture, forcing them to manually switch away from VybeClip before + // they can even start. Hiding (not closing) it lets the existing + // restore-on-return logic in createWindow()/focusOrCreateMainWindow() + // bring it back later exactly as before. + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.hide(); + } + const existingHud = getHudOverlayWindow(); if (existingHud) { showHudOverlayFromTray(); @@ -1040,6 +1051,17 @@ app.whenReady().then(async () => { hud.close(); } + // The HUD also closes on a successful recording, right as switch-to-editor + // hands off to the editor window - in that case Studio should stay hidden + // so the editor is the only thing on screen. But if the user canceled + // before ever reaching the editor, Studio (hidden in + // showOrCreateRecordingControls) needs to come back, or they're left + // with no visible VybeClip window at all until they happen to click the + // Dock icon. + if (!getExistingEditorWindow()) { + restoreWindowSafely(mainWindow); + } + // If this was the last window (or we are in a state where we should quit), do it. // We use a small delay to allow window.close() to propagate. setTimeout(() => { @@ -1101,6 +1123,11 @@ app.whenReady().then(async () => { return { success: true }; }); + ipcMain.handle("set-hud-overlay-webcam-preview-active", (_event, active: boolean) => { + setHudOverlayWebcamPreviewActive(Boolean(active)); + return { success: true }; + }); + if (IS_SMOKE_EXPORT || process.env.RECORDLY_DEV_OPEN_RECORDING_INPUT) { await logSmokeExportGpuDiagnostics(); if (IS_SMOKE_EXPORT) { diff --git a/electron/preload.ts b/electron/preload.ts index bd042b27e..f1425b88e 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -488,6 +488,9 @@ contextBridge.exposeInMainWorld("electronAPI", { showRecordingControls: () => { return ipcRenderer.invoke("show-recording-controls"); }, + setHudOverlayWebcamPreviewActive: (active: boolean) => { + return ipcRenderer.invoke("set-hud-overlay-webcam-preview-active", active); + }, selectSource: (source: ProcessedDesktopSource) => { return ipcRenderer.invoke("select-source", source); }, diff --git a/electron/windows.ts b/electron/windows.ts index 3deae81c8..16b15f193 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -29,6 +29,17 @@ let hudOverlayIgnoringMouse = true; let hudOverlaySourceSelectionActive = false; let hudOverlayMouseReassertTimer: NodeJS.Timeout | null = null; let hudOverlayRecordingActive = false; +// The HUD window normally shrinks to a compact, non-click-through band during +// active recording (see isHudOverlayCanvasActive) so the stop/pause controls +// stay reliably clickable without depending on hover-based passthrough +// toggling. That compact band is only tall enough for the HUD bar itself, +// which leaves nowhere for the draggable webcam preview bubble to go - it +// gets visually clipped at the window's real edge and can't move vertically +// at all. While a webcam preview is showing, use the full work area instead; +// the existing hover-driven mouse-passthrough toggling (already wired to the +// bubble's own pointer events) keeps clicks passing through everywhere except +// the HUD bar/bubble themselves, independent of window size. +let hudOverlayWebcamPreviewActive = false; let countdownWindow: BrowserWindow | null = null; let updateToastWindow: BrowserWindow | null = null; @@ -198,11 +209,22 @@ function isHudOverlayCanvasActive(): boolean { function getHudOverlayBounds() { const { workArea } = getHudOverlayDisplay(); - return getHudOverlayWindowBounds( - workArea, - isHudOverlayCanvasActive(), - hudOverlayFallbackExpanded, - ); + // Deliberately not just isHudOverlayCanvasActive(): that flag also gates + // the monitor-boundary-crossing drag relocation logic below, which should + // stay tied to the platform/passthrough check alone. Sizing the window + // itself additionally needs to widen for an active webcam preview so the + // draggable bubble has somewhere to go, even mid-recording. + const needsFullCanvas = isHudOverlayCanvasActive() || hudOverlayWebcamPreviewActive; + return getHudOverlayWindowBounds(workArea, needsFullCanvas, hudOverlayFallbackExpanded); +} + +export function setHudOverlayWebcamPreviewActive(active: boolean): void { + const next = Boolean(active); + if (next === hudOverlayWebcamPreviewActive) { + return; + } + hudOverlayWebcamPreviewActive = next; + applyHudOverlayBounds(); } function applyHudOverlayBounds() { diff --git a/src/components/launch/hooks/useWebcamPreviewOverlay.ts b/src/components/launch/hooks/useWebcamPreviewOverlay.ts index deba0d279..8afbd9a1f 100644 --- a/src/components/launch/hooks/useWebcamPreviewOverlay.ts +++ b/src/components/launch/hooks/useWebcamPreviewOverlay.ts @@ -59,6 +59,19 @@ export function useWebcamPreviewOverlay({ const shouldStreamWebcamPreview = webcamEnabled && (showRecordingWebcamPreview || (showWebcamControls && webcamPopoverOpen)); + useEffect(() => { + // The HUD overlay window shrinks to a compact, non-click-through band + // during active recording so the stop/pause controls stay reliably + // clickable (see windows.ts). That band is only tall enough for the HUD + // bar itself, leaving the draggable webcam bubble stuck at the bottom + // edge with no vertical room. Tell the main process when a preview is + // actually showing so it can widen the window to fit it. + void window.electronAPI?.setHudOverlayWebcamPreviewActive?.(showRecordingWebcamPreview); + return () => { + void window.electronAPI?.setHudOverlayWebcamPreviewActive?.(false); + }; + }, [showRecordingWebcamPreview]); + useEffect(() => { if (!webcamEnabled) { webcamPreviewOffsetRef.current = DEFAULT_WEBCAM_PREVIEW_OFFSET;