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;