From 7a217022a65875037ddc3b98297da4a7e5e45390 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Sun, 7 Jun 2026 00:59:00 +0200 Subject: [PATCH] ddsfsf --- UMSETZUNGSSTATUS.md | 3 +- public/assets/desktop/desktop.css | 13 +++ public/assets/desktop/desktop.js | 157 ++++++++++++++++++++++++------ 3 files changed, 144 insertions(+), 29 deletions(-) diff --git a/UMSETZUNGSSTATUS.md b/UMSETZUNGSSTATUS.md index 139be164..a7613f3d 100644 --- a/UMSETZUNGSSTATUS.md +++ b/UMSETZUNGSSTATUS.md @@ -149,7 +149,7 @@ Phase 3 Fenster-Manager: - `ERLEDIGT`: Fenster oeffnen und schliessen - `ERLEDIGT`: Fenster fokussieren - `ERLEDIGT`: Fenster minimieren -- `TEILWEISE`: Fenster maximieren +- `ERLEDIGT`: Fenster maximieren - `ERLEDIGT`: z-index-Verwaltung - `OFFEN`: Position und Groesse merken @@ -237,6 +237,7 @@ Vorhanden: - Skin-Resolver - Default-Workspace- und Widget-State - einfacher Window-Manager +- Window-Controls fuer minimieren, maximieren und schliessen - CSS/JS fuer Shell, Fenster, Taskbar, Startmenue, Widgets und Uhr Hauptdateien: diff --git a/public/assets/desktop/desktop.css b/public/assets/desktop/desktop.css index 5ea6ed23..0a32f6e5 100644 --- a/public/assets/desktop/desktop.css +++ b/public/assets/desktop/desktop.css @@ -223,6 +223,10 @@ h1 { display: none; } +.window.is-maximized .window-header { + cursor: default; +} + .window-header { display: flex; justify-content: space-between; @@ -291,6 +295,15 @@ h1 { cursor: pointer; } +.taskbar-app-button.is-focused { + background: var(--accent); + color: #0f172a; +} + +.taskbar-app-button.is-minimized { + opacity: 0.62; +} + .start-menu { position: absolute; left: 24px; diff --git a/public/assets/desktop/desktop.js b/public/assets/desktop/desktop.js index 3c7a5543..477af0cf 100644 --- a/public/assets/desktop/desktop.js +++ b/public/assets/desktop/desktop.js @@ -38,9 +38,12 @@ if (payloadNode) { return; } + record.state.minimized = false; + record.node.classList.remove('is-minimized'); zIndex += 1; record.node.style.zIndex = String(zIndex); record.node.classList.add('is-focused'); + syncTaskbar(); }; const closeStartMenu = () => { @@ -52,16 +55,91 @@ if (payloadNode) { startButton?.setAttribute('aria-expanded', 'false'); }; + const applyWindowLayout = (record) => { + const { x, y, width, height } = record.layout; + record.node.style.left = `${x}px`; + record.node.style.top = `${y}px`; + record.node.style.width = `${width}px`; + record.node.style.height = `${height}px`; + }; + + const toggleMinimize = (windowId) => { + const record = windows.get(windowId); + if (!record) { + return; + } + + record.state.minimized = !record.state.minimized; + record.node.classList.toggle('is-minimized', record.state.minimized); + + if (!record.state.minimized) { + focusWindow(windowId); + } else { + syncTaskbar(); + } + }; + + const toggleMaximize = (windowId) => { + const record = windows.get(windowId); + if (!record) { + return; + } + + if (!record.state.maximized) { + record.restoreLayout = { ...record.layout }; + record.state.maximized = true; + record.node.classList.add('is-maximized'); + record.node.style.left = '24px'; + record.node.style.top = '24px'; + record.node.style.width = 'calc(100% - 48px)'; + record.node.style.height = 'calc(100% - 48px)'; + focusWindow(windowId); + return; + } + + record.state.maximized = false; + record.node.classList.remove('is-maximized'); + if (record.restoreLayout) { + record.layout = { ...record.restoreLayout }; + applyWindowLayout(record); + } + focusWindow(windowId); + }; + + const closeWindow = (windowId) => { + const record = windows.get(windowId); + if (!record) { + return; + } + + windows.delete(windowId); + record.node.remove(); + syncTaskbar(); + }; + const syncTaskbar = () => { + if (!taskbarRoot) { + return; + } + taskbarRoot.innerHTML = ''; windows.forEach((record) => { const button = document.createElement('button'); button.type = 'button'; button.className = 'taskbar-app-button'; + if (record.state.minimized) { + button.classList.add('is-minimized'); + } else if (record.node.classList.contains('is-focused')) { + button.classList.add('is-focused'); + } button.textContent = record.title; button.addEventListener('click', () => { - record.node.classList.toggle('is-minimized'); + if (record.state.minimized) { + toggleMinimize(record.id); + return; + } + focusWindow(record.id); }); taskbarRoot.appendChild(button); @@ -81,11 +159,19 @@ if (payloadNode) { let dragging = false; header.addEventListener('pointerdown', (event) => { + if (record.state.maximized) { + return; + } + + if (event.target instanceof Element && event.target.closest('.window-action')) { + return; + } + dragging = true; startX = event.clientX; startY = event.clientY; - originX = parseInt(record.node.style.left, 10); - originY = parseInt(record.node.style.top, 10); + originX = record.layout.x; + originY = record.layout.y; header.setPointerCapture(event.pointerId); focusWindow(record.id); }); @@ -95,8 +181,9 @@ if (payloadNode) { return; } - record.node.style.left = `${originX + event.clientX - startX}px`; - record.node.style.top = `${originY + event.clientY - startY}px`; + record.layout.x = originX + event.clientX - startX; + record.layout.y = originY + event.clientY - startY; + applyWindowLayout(record); }); header.addEventListener('pointerup', () => { @@ -107,10 +194,6 @@ if (payloadNode) { const createWindow = (definition) => { const node = document.createElement('article'); node.className = 'window'; - node.style.left = `${definition.x}px`; - node.style.top = `${definition.y}px`; - node.style.width = `${definition.width}px`; - node.style.height = `${definition.height}px`; node.innerHTML = `
${definition.title} @@ -131,48 +214,54 @@ if (payloadNode) { title: definition.title, node, definition, + layout: { + x: definition.x, + y: definition.y, + width: definition.width, + height: definition.height, + }, + restoreLayout: null, + state: { + minimized: false, + maximized: false, + }, }; + applyWindowLayout(record); + node.addEventListener('mousedown', () => focusWindow(record.id)); node.querySelectorAll('.window-action').forEach((action) => { - action.addEventListener('click', () => { + action.addEventListener('click', (event) => { + event.stopPropagation(); + const actionName = action.dataset.action; if (actionName === 'close') { - windows.delete(record.id); - node.remove(); - syncTaskbar(); + closeWindow(record.id); return; } if (actionName === 'minimize') { - node.classList.toggle('is-minimized'); - syncTaskbar(); + toggleMinimize(record.id); return; } if (actionName === 'maximize') { - node.style.left = '24px'; - node.style.top = '24px'; - node.style.width = 'calc(100% - 48px)'; - node.style.height = 'calc(100% - 48px)'; - focusWindow(record.id); + toggleMaximize(record.id); } }); }); windows.set(record.id, record); - windowsRoot.appendChild(node); + windowsRoot?.appendChild(node); wireDragging(record); focusWindow(record.id); - syncTaskbar(); }; const openApp = (app) => { const existing = Array.from(windows.values()).find((record) => record.definition.app_id === app.app_id); if (existing) { - existing.node.classList.remove('is-minimized'); focusWindow(existing.id); return; } @@ -190,6 +279,10 @@ if (payloadNode) { }; const renderWidgets = () => { + if (!widgetZoneRoot) { + return; + } + widgetZoneRoot.innerHTML = ''; payload.widgets.registry @@ -212,6 +305,10 @@ if (payloadNode) { }; const renderMenuWidgets = () => { + if (!startMenuWidgets) { + return; + } + startMenuWidgets.innerHTML = ''; payload.widgets.registry.forEach((widget) => { @@ -274,6 +371,10 @@ if (payloadNode) { }; const renderStartMenu = () => { + if (!startMenuApps || !startMenuActions) { + return; + } + startMenuApps.innerHTML = ''; startMenuActions.innerHTML = ''; @@ -324,18 +425,18 @@ if (payloadNode) { ${app.title} `; icon.addEventListener('click', () => openApp(app)); - iconsRoot.appendChild(icon); + iconsRoot?.appendChild(icon); }); startButton?.setAttribute('aria-expanded', 'false'); startButton?.addEventListener('click', () => { - if (!startMenu) { + if (!startMenu || !startButton) { return; } - const isOpen = !startMenu.hidden; - startMenu.hidden = isOpen; - startButton.setAttribute('aria-expanded', String(!isOpen)); + const willOpen = startMenu.hidden; + startMenu.hidden = !willOpen; + startButton.setAttribute('aria-expanded', String(willOpen)); }); document.addEventListener('click', (event) => {