mirror of
https://github.com/siddharthvaddem/openscreen.git
synced 2026-09-01 15:35:00 +08:00
hud overlay UX improvements
This commit is contained in:
+27
-10
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
},
|
||||
|
||||
Vendored
+2
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -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();
|
||||
|
||||
@@ -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')
|
||||
|
||||
+27
-10
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<number | null>(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 (
|
||||
<div className="w-full h-full flex items-center bg-transparent">
|
||||
<div
|
||||
className={`w-full max-w-3xl mx-auto flex items-center justify-between px-3 py-1.5 ${styles.electronDrag}`}
|
||||
className={`w-full max-w-[500px] mx-auto flex items-center justify-between px-4 py-2 ${styles.electronDrag}`}
|
||||
style={{
|
||||
borderRadius: 14,
|
||||
background: 'linear-gradient(135deg, rgba(30,30,40,0.85) 0%, rgba(20,20,30,0.75) 100%)',
|
||||
backdropFilter: 'blur(24px) saturate(180%)',
|
||||
WebkitBackdropFilter: 'blur(24px) saturate(180%)',
|
||||
boxShadow: '0 4px 16px 0 rgba(0,0,0,0.24), 0 1px 3px 0 rgba(0,0,0,0.12) inset',
|
||||
border: '1px solid rgba(80,80,120,0.18)',
|
||||
minHeight: 36,
|
||||
borderRadius: 16,
|
||||
background: 'linear-gradient(135deg, rgba(30,30,40,0.92) 0%, rgba(20,20,30,0.85) 100%)',
|
||||
backdropFilter: 'blur(32px) saturate(180%)',
|
||||
WebkitBackdropFilter: 'blur(32px) saturate(180%)',
|
||||
boxShadow: '0 4px 24px 0 rgba(0,0,0,0.28), 0 1px 3px 0 rgba(0,0,0,0.14) inset',
|
||||
border: '1px solid rgba(80,80,120,0.22)',
|
||||
minHeight: 44,
|
||||
}}
|
||||
>
|
||||
<div className={`flex items-center gap-1 ${styles.electronDrag}`}>
|
||||
<RxDragHandleDots2 size={16} className="text-white/40" />
|
||||
</div>
|
||||
<div className={`flex items-center gap-1 ${styles.electronDrag}`}> <RxDragHandleDots2 size={18} className="text-white/40" /> </div>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
@@ -81,11 +119,11 @@ export function LaunchWindow() {
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-left text-xs ${styles.electronNoDrag}`}
|
||||
onClick={openSourceSelector}
|
||||
>
|
||||
<MdMonitor size={13} className="text-white" />
|
||||
<MdMonitor size={14} className="text-white" />
|
||||
{truncateText(selectedSource, 6)}
|
||||
</Button>
|
||||
|
||||
<div className="w-px h-5 bg-white/30" />
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
@@ -96,26 +134,52 @@ export function LaunchWindow() {
|
||||
>
|
||||
{recording ? (
|
||||
<>
|
||||
<FaRegStopCircle size={13} className="text-red-400" />
|
||||
<span className="text-red-400">Stop</span>
|
||||
<FaRegStopCircle size={14} className="text-red-400" />
|
||||
<span className="text-red-400">{formatTime(elapsed)}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BsRecordCircle size={13} className={hasSelectedSource ? "text-white" : "text-white/50"} />
|
||||
<BsRecordCircle size={14} className={hasSelectedSource ? "text-white" : "text-white/50"} />
|
||||
<span className={hasSelectedSource ? "text-white" : "text-white/50"}>Record</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
|
||||
<div className="w-px h-6 bg-white/30" />
|
||||
|
||||
<div className="w-px h-5 bg-white/30" />
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
onClick={openVideoFile}
|
||||
className={`gap-1 bg-transparent hover:bg-transparent px-0 flex-1 text-right text-xs ${styles.electronNoDrag} folderButton`}
|
||||
className={`gap-1 text-white bg-transparent hover:bg-transparent px-0 flex-1 text-right text-xs ${styles.electronNoDrag} ${styles.folderButton}`}
|
||||
>
|
||||
<FaFolderMinus size={13} className="text-white" />
|
||||
<FaFolderMinus size={14} className="text-white" />
|
||||
<span className={styles.folderText}>Load</span>
|
||||
</Button>
|
||||
|
||||
{/* Separator before hide/close buttons */}
|
||||
<div className="w-px h-6 bg-white/30 mx-2" />
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
className={`ml-2 ${styles.electronNoDrag} hudOverlayButton`}
|
||||
title="Hide HUD"
|
||||
onClick={sendHudOverlayHide}
|
||||
>
|
||||
<FiMinus size={18} style={{ color: '#fff', opacity: 0.7 }} />
|
||||
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="icon"
|
||||
className={`ml-1 ${styles.electronNoDrag} hudOverlayButton`}
|
||||
title="Close App"
|
||||
onClick={sendHudOverlayClose}
|
||||
>
|
||||
<FiX size={18} style={{ color: '#fff', opacity: 0.7 }} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user