diff --git a/dist-electron/main.js b/dist-electron/main.js index 2d968286..3fa89090 100644 --- a/dist-electron/main.js +++ b/dist-electron/main.js @@ -1,4 +1,4 @@ -import { screen, BrowserWindow, ipcMain, desktopCapturer, shell, app, dialog, nativeImage, Tray, Menu } from "electron"; +import { ipcMain, screen, BrowserWindow, desktopCapturer, shell, app, dialog, nativeImage, Tray, Menu } from "electron"; import { fileURLToPath } from "node:url"; import path from "node:path"; import fs from "node:fs/promises"; @@ -6,20 +6,26 @@ const __dirname$1 = path.dirname(fileURLToPath(import.meta.url)); const APP_ROOT = path.join(__dirname$1, ".."); const VITE_DEV_SERVER_URL$1 = process.env["VITE_DEV_SERVER_URL"]; const RENDERER_DIST$1 = path.join(APP_ROOT, "dist"); +let hudOverlayWindow = null; +ipcMain.on("hud-overlay-hide", () => { + if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) { + hudOverlayWindow.minimize(); + } +}); function createHudOverlayWindow() { const primaryDisplay = screen.getPrimaryDisplay(); const { workArea } = primaryDisplay; - const windowWidth = 350; - const windowHeight = 80; - const x = Math.floor(workArea.x + workArea.width - windowWidth - 5); - const y = Math.floor(workArea.y + workArea.height - (windowHeight - 30)); + const windowWidth = 500; + const windowHeight = 100; + const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2); + const y = Math.floor(workArea.y + workArea.height - windowHeight - 5); const win = new BrowserWindow({ width: windowWidth, height: windowHeight, - minWidth: 350, - maxWidth: 350, - minHeight: 80, - maxHeight: 80, + minWidth: 500, + maxWidth: 500, + minHeight: 100, + maxHeight: 100, x, y, frame: false, @@ -38,6 +44,12 @@ function createHudOverlayWindow() { win.webContents.on("did-finish-load", () => { win == null ? void 0 : win.webContents.send("main-process-message", (/* @__PURE__ */ new Date()).toLocaleString()); }); + hudOverlayWindow = win; + win.on("closed", () => { + if (hudOverlayWindow === win) { + hudOverlayWindow = null; + } + }); if (VITE_DEV_SERVER_URL$1) { win.loadURL(VITE_DEV_SERVER_URL$1 + "?windowType=hud-overlay"); } else { @@ -350,6 +362,12 @@ app.on("activate", () => { } }); app.whenReady().then(async () => { + const { ipcMain: ipcMain2 } = await import("electron"); + ipcMain2.on("hud-overlay-close", () => { + if (process.platform === "darwin") { + app.quit(); + } + }); await ensureRecordingsDir(); registerIpcHandlers( createEditorWindowWrapper, @@ -361,7 +379,6 @@ app.whenReady().then(async () => { if (recording) { if (!tray) createTray(); updateTrayMenu(); - if (mainWindow) mainWindow.minimize(); } else { if (tray) { tray.destroy(); diff --git a/dist-electron/preload.mjs b/dist-electron/preload.mjs index 7fe77fee..42f5816b 100644 --- a/dist-electron/preload.mjs +++ b/dist-electron/preload.mjs @@ -1,6 +1,12 @@ "use strict"; const electron = require("electron"); electron.contextBridge.exposeInMainWorld("electronAPI", { + hudOverlayHide: () => { + electron.ipcRenderer.send("hud-overlay-hide"); + }, + hudOverlayClose: () => { + electron.ipcRenderer.send("hud-overlay-close"); + }, getAssetBasePath: async () => { return await electron.ipcRenderer.invoke("get-asset-base-path"); }, diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index fed165e2..9328bbba 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -39,6 +39,8 @@ interface Window { setCurrentVideoPath: (path: string) => Promise<{ success: boolean }> getCurrentVideoPath: () => Promise<{ success: boolean; path?: string }> clearCurrentVideoPath: () => Promise<{ success: boolean }> + hudOverlayHide: () => void; + hudOverlayClose: () => void; } } diff --git a/electron/main.ts b/electron/main.ts index 27226329..0cd4cbd6 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -108,6 +108,13 @@ app.on('activate', () => { // Register all IPC handlers when app is ready app.whenReady().then(async () => { + // Listen for HUD overlay quit event (macOS only) + const { ipcMain } = await import('electron'); + ipcMain.on('hud-overlay-close', () => { + if (process.platform === 'darwin') { + app.quit(); + } + }); // Ensure recordings directory exists await ensureRecordingsDir() @@ -121,7 +128,6 @@ app.whenReady().then(async () => { if (recording) { if (!tray) createTray(); updateTrayMenu(); - if (mainWindow) mainWindow.minimize(); } else { if (tray) { tray.destroy(); diff --git a/electron/preload.ts b/electron/preload.ts index f853d101..e8644e40 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -1,6 +1,12 @@ import { contextBridge, ipcRenderer } from 'electron' contextBridge.exposeInMainWorld('electronAPI', { + hudOverlayHide: () => { + ipcRenderer.send('hud-overlay-hide'); + }, + hudOverlayClose: () => { + ipcRenderer.send('hud-overlay-close'); + }, getAssetBasePath: async () => { // ask main process for the correct base path (production vs dev) return await ipcRenderer.invoke('get-asset-base-path') diff --git a/electron/windows.ts b/electron/windows.ts index c0759b4b..c5658f79 100644 --- a/electron/windows.ts +++ b/electron/windows.ts @@ -1,4 +1,5 @@ import { BrowserWindow, screen } from 'electron' +import { ipcMain } from 'electron' import path from 'node:path' import { fileURLToPath } from 'node:url' @@ -8,25 +9,32 @@ const APP_ROOT = path.join(__dirname, '..') const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL'] const RENDERER_DIST = path.join(APP_ROOT, 'dist') +let hudOverlayWindow: BrowserWindow | null = null; + +ipcMain.on('hud-overlay-hide', () => { + if (hudOverlayWindow && !hudOverlayWindow.isDestroyed()) { + hudOverlayWindow.minimize(); + } +}); + export function createHudOverlayWindow(): BrowserWindow { const primaryDisplay = screen.getPrimaryDisplay(); const { workArea } = primaryDisplay; - // Define the desired window size - const windowWidth = 350; - const windowHeight = 80; - const x = Math.floor(workArea.x + workArea.width - windowWidth - 5); // Align to the right edge of the work area - // const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2); // Center horizontally within the work area - const y = Math.floor(workArea.y + workArea.height - (windowHeight - 30)); + const windowWidth = 500; + const windowHeight = 100; + + const x = Math.floor(workArea.x + (workArea.width - windowWidth) / 2); + const y = Math.floor(workArea.y + workArea.height - windowHeight - 5); const win = new BrowserWindow({ width: windowWidth, height: windowHeight, - minWidth: 350, - maxWidth: 350, - minHeight: 80, - maxHeight: 80, + minWidth: 500, + maxWidth: 500, + minHeight: 100, + maxHeight: 100, x: x, y: y, frame: false, @@ -48,6 +56,15 @@ export function createHudOverlayWindow(): BrowserWindow { win?.webContents.send('main-process-message', (new Date).toLocaleString()) }) + hudOverlayWindow = win; + + win.on('closed', () => { + if (hudOverlayWindow === win) { + hudOverlayWindow = null; + } + }); + + if (VITE_DEV_SERVER_URL) { win.loadURL(VITE_DEV_SERVER_URL + '?windowType=hud-overlay') } else { diff --git a/src/components/launch/LaunchWindow.module.css b/src/components/launch/LaunchWindow.module.css index 2eb5eb1f..12d33b54 100644 --- a/src/components/launch/LaunchWindow.module.css +++ b/src/components/launch/LaunchWindow.module.css @@ -6,10 +6,32 @@ -webkit-app-region: no-drag; } + .folderButton { cursor: pointer; + display: flex; + align-items: center; + gap: 4px; } -.folderButton:hover { +.folderText { + color: #cbd5e1; + transition: text-decoration 0.15s; +} + +.folderButton:hover .folderText { text-decoration: underline; +} + +.hudOverlayButton { + cursor: pointer; + background: none; + border: none; + color: #fff; + opacity: 0.7; + transition: opacity 0.15s; +} +.hudOverlayButton:hover { + opacity: 0.7; + background: none !important; } \ No newline at end of file diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index e952c373..f6f6780e 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -7,9 +7,37 @@ import { FaRegStopCircle } from "react-icons/fa"; import { MdMonitor } from "react-icons/md"; import { RxDragHandleDots2 } from "react-icons/rx"; import { FaFolderMinus } from "react-icons/fa6"; +import { FiMinus, FiX } from "react-icons/fi"; export function LaunchWindow() { const { recording, toggleRecording } = useScreenRecorder(); + const [recordingStart, setRecordingStart] = useState(null); + const [elapsed, setElapsed] = useState(0); + + useEffect(() => { + let timer: NodeJS.Timeout | null = null; + if (recording) { + if (!recordingStart) setRecordingStart(Date.now()); + timer = setInterval(() => { + if (recordingStart) { + setElapsed(Math.floor((Date.now() - recordingStart) / 1000)); + } + }, 1000); + } else { + setRecordingStart(null); + setElapsed(0); + if (timer) clearInterval(timer); + } + return () => { + if (timer) clearInterval(timer); + }; + }, [recording, recordingStart]); + + const formatTime = (seconds: number) => { + const m = Math.floor(seconds / 60).toString().padStart(2, '0'); + const s = (seconds % 60).toString().padStart(2, '0'); + return `${m}:${s}`; + }; const [selectedSource, setSelectedSource] = useState("Screen"); const [hasSelectedSource, setHasSelectedSource] = useState(false); @@ -57,23 +85,33 @@ export function LaunchWindow() { } }; + // IPC events for hide/close + const sendHudOverlayHide = () => { + if (window.electronAPI && window.electronAPI.hudOverlayHide) { + window.electronAPI.hudOverlayHide(); + } + }; + const sendHudOverlayClose = () => { + if (window.electronAPI && window.electronAPI.hudOverlayClose) { + window.electronAPI.hudOverlayClose(); + } + }; + return (
-
- -
+
-
+
+ + +
-
+ + {/* Separator before hide/close buttons */} +
+ + +