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 = ` +