From 8ddda451f305f630663cd94cad9ee194e96a10fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Tue, 13 Jan 2026 18:41:22 +0100 Subject: [PATCH] Add fake frame for electron window --- src/main/resources/static/css/styles.css | 69 +++++++++++++++++++++++ src/main/resources/static/js/broadcast.js | 4 +- src/main/resources/static/js/electron.js | 63 ++++++++++++++++++++- 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/src/main/resources/static/css/styles.css b/src/main/resources/static/css/styles.css index 29c82c1..bd7d4d2 100644 --- a/src/main/resources/static/css/styles.css +++ b/src/main/resources/static/css/styles.css @@ -2,6 +2,10 @@ box-sizing: border-box; } +:root { + --window-frame-height: 36px; +} + p { margin: 0; } @@ -1142,6 +1146,71 @@ button:disabled:hover { background: transparent; } +.window-frame { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: var(--window-frame-height); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 12px; + background: rgba(15, 23, 42, 0.9); + border-bottom: 1px solid rgba(30, 41, 59, 0.7); + z-index: 5; + -webkit-app-region: drag; +} + +.broadcast-body.has-window-frame #broadcast-canvas, +.broadcast-body.has-window-frame .broadcast-script-layer { + top: var(--window-frame-height); +} + +.window-frame-title { + font-size: 12px; + letter-spacing: 0.3px; + color: #cbd5f5; + text-transform: uppercase; +} + +.window-frame-controls { + display: flex; + align-items: center; + gap: 6px; + -webkit-app-region: no-drag; +} + +.window-control { + width: 30px; + height: 24px; + padding: 0; + border-radius: 6px; + background: rgba(148, 163, 184, 0.2); + border: 1px solid rgba(148, 163, 184, 0.25); + color: #e2e8f0; + font-size: 16px; + line-height: 1; + box-shadow: none; +} + +.window-control:hover { + background: rgba(148, 163, 184, 0.35); +} + +.window-control:active { + transform: none; +} + +.window-control-close { + background: rgba(239, 68, 68, 0.3); + border-color: rgba(239, 68, 68, 0.4); +} + +.window-control-close:hover { + background: rgba(239, 68, 68, 0.5); +} + .panel { margin-top: 24px; padding: 16px; diff --git a/src/main/resources/static/js/broadcast.js b/src/main/resources/static/js/broadcast.js index f3c15f9..a1a5a50 100644 --- a/src/main/resources/static/js/broadcast.js +++ b/src/main/resources/static/js/broadcast.js @@ -1,9 +1,11 @@ import { BroadcastRenderer } from "./broadcast/renderer.js"; import { connectTwitchChat } from "./broadcast/twitchChat.js"; -import { setUpElectronWindowResizeListener } from "./electron.js"; +import { setUpElectronWindowFrame, setUpElectronWindowResizeListener } from "./electron.js"; const canvas = document.getElementById("broadcast-canvas"); const scriptLayer = document.getElementById("broadcast-script-layer"); +setUpElectronWindowFrame(); + const renderer = new BroadcastRenderer({ canvas, scriptLayer, broadcaster, showToast }); const disconnectChat = connectTwitchChat(broadcaster, ({ channel, displayName, message }) => { console.log(`[twitch:${broadcaster}] ${displayName}: ${message}`); diff --git a/src/main/resources/static/js/electron.js b/src/main/resources/static/js/electron.js index abcb3de..2237f73 100644 --- a/src/main/resources/static/js/electron.js +++ b/src/main/resources/static/js/electron.js @@ -1,9 +1,63 @@ -export function setUpElectronWindowResizeListener(canvas) { - if ( +function canManageElectronWindow() { + return ( typeof window !== "undefined" && window.store && typeof window.store.setWindowSize === "function" + ); +} + +function getWindowFrameHeight() { + const height = getComputedStyle(document.documentElement) + .getPropertyValue("--window-frame-height") + .trim(); + const parsed = Number.parseInt(height.replace("px", ""), 10); + return Number.isNaN(parsed) ? 0 : parsed; +} + +export function setUpElectronWindowFrame() { + if ( + typeof window === "undefined" || + !window.store || + typeof window.store.minimizeWindow !== "function" ) { + return false; + } + + document.body.classList.add("has-window-frame"); + + const frame = document.createElement("div"); + frame.className = "window-frame"; + frame.setAttribute("role", "presentation"); + frame.innerHTML = ` +
Imgfloat
+
+ + +
+ `; + document.body.appendChild(frame); + + frame.querySelectorAll("[data-window-action]").forEach((button) => { + button.addEventListener("click", () => { + const action = button.dataset.windowAction; + if (action === "minimize") { + window.store.minimizeWindow(); + } + if (action === "close") { + window.store.closeWindow(); + } + }); + }); + + return true; +} + +export function setUpElectronWindowResizeListener(canvas) { + if (canManageElectronWindow()) { console.info("Electron environment detected, setting up resize listener."); } else { console.info("Not running in Electron environment, skipping resize listener setup."); @@ -12,9 +66,12 @@ export function setUpElectronWindowResizeListener(canvas) { const resize = () => { const rect = canvas.getBoundingClientRect(); + const frameHeight = document.body.classList.contains("has-window-frame") + ? getWindowFrameHeight() + : 0; window.store.setWindowSize( Math.ceil(rect.width), - Math.ceil(rect.height) + Math.ceil(rect.height + frameHeight) ); };