/** * pc-live | Window Manager * Handles: create, focus, drag, resize, maximize, minimize, close, taskbar */ const WindowManager = (() => { const layer = document.getElementById('window-layer'); const taskbarApps = document.getElementById('taskbar-apps'); let zCounter = 10; const windows = new Map(); // id -> { el, tbBtn, minimized, maximized, preMax, title, icon } /* ── Helpers ──────────────────────────────────────────── */ function nextZ() { return ++zCounter; } function clampPos(el) { const lw = layer.clientWidth; const lh = layer.clientHeight; let l = parseInt(el.style.left) || 0; let t = parseInt(el.style.top) || 0; const w = el.offsetWidth; const h = el.offsetHeight; el.style.left = Math.min(Math.max(0, l), lw - w) + 'px'; el.style.top = Math.min(Math.max(0, t), lh - Math.max(40, h - 300)) + 'px'; } /* ── Drag ─────────────────────────────────────────────── */ function makeDraggable(el, handle) { let startX, startY, startL, startT; handle.addEventListener('mousedown', (e) => { if (e.target.classList.contains('wc-btn')) return; if (e.target.classList.contains('wc-resize')) return; const win = windows.get(el.dataset.wid); if (win && win.maximized) return; startX = e.clientX; startY = e.clientY; startL = parseInt(el.style.left) || 0; startT = parseInt(el.style.top) || 0; focus(el.dataset.wid); const mm = (e2) => { el.style.left = (startL + e2.clientX - startX) + 'px'; el.style.top = (startT + e2.clientY - startY) + 'px'; }; const mu = () => { clampPos(el); document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); }; document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); }); // Double-click titlebar → toggle maximize handle.addEventListener('dblclick', (e) => { if (e.target.classList.contains('wc-btn')) return; toggleMaximize(el.dataset.wid); }); } /* ── Resize ───────────────────────────────────────────── */ function makeResizable(el, id) { const dirs = ['n', 's', 'e', 'w', 'nw', 'ne', 'sw', 'se']; dirs.forEach(dir => { const h = document.createElement('div'); h.className = 'wc-resize wc-resize-' + dir; el.appendChild(h); let startX, startY, startL, startT, startW, startH; const MIN_W = 320, MIN_H = 200; h.addEventListener('mousedown', (e) => { const win = windows.get(id); if (!win || win.maximized) return; e.stopPropagation(); focus(id); startX = e.clientX; startY = e.clientY; startL = parseInt(el.style.left) || 0; startT = parseInt(el.style.top) || 0; startW = el.offsetWidth; startH = el.offsetHeight; const mm = (e2) => { const dx = e2.clientX - startX; const dy = e2.clientY - startY; let newL = startL, newT = startT, newW = startW, newH = startH; if (dir.includes('e')) newW = Math.max(MIN_W, startW + dx); if (dir.includes('w')) { newW = Math.max(MIN_W, startW - dx); newL = startL + startW - newW; } if (dir.includes('s')) newH = Math.max(MIN_H, startH + dy); if (dir.includes('n')) { newH = Math.max(MIN_H, startH - dy); newT = startT + startH - newH; } el.style.left = newL + 'px'; el.style.top = newT + 'px'; el.style.width = newW + 'px'; el.style.height = newH + 'px'; }; const mu = () => { document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); }; document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); }); }); } /* ── Focus ────────────────────────────────────────────── */ function focus(id) { const win = windows.get(id); if (!win) return; windows.forEach((w, wid) => { w.el.classList.toggle('focused', wid === id); w.tbBtn && w.tbBtn.classList.toggle('active', wid === id); }); win.el.style.zIndex = nextZ(); } /* ── Maximize / Restore ───────────────────────────────── */ function maximize(id) { const win = windows.get(id); if (!win || win.maximized) return; win.preMax = { left: win.el.style.left, top: win.el.style.top, width: win.el.style.width, height: win.el.style.height, }; win.maximized = true; win.el.classList.add('maximized'); win.el.style.left = '0'; win.el.style.top = '0'; win.el.style.width = layer.clientWidth + 'px'; win.el.style.height = layer.clientHeight + 'px'; const btn = win.el.querySelector('.wc-maximize'); if (btn) btn.title = 'Restore'; focus(id); } function unmaximize(id) { const win = windows.get(id); if (!win || !win.maximized || !win.preMax) return; win.maximized = false; win.el.classList.remove('maximized'); win.el.style.left = win.preMax.left; win.el.style.top = win.preMax.top; win.el.style.width = win.preMax.width; win.el.style.height = win.preMax.height; win.preMax = null; const btn = win.el.querySelector('.wc-maximize'); if (btn) btn.title = 'Maximize'; focus(id); } function toggleMaximize(id) { const win = windows.get(id); if (!win) return; if (win.maximized) unmaximize(id); else maximize(id); } /* ── Create ───────────────────────────────────────────── */ function create({ id, title, icon = '🖥', width = 700, height = 480, content }) { if (windows.has(id)) { restore(id); return windows.get(id).el; } const lw = layer.clientWidth; const lh = layer.clientHeight; const left = Math.max(20, Math.floor(lw / 2 - width / 2)); const top = Math.max(20, Math.floor(lh / 2 - height / 2)); const el = document.createElement('div'); el.className = 'window fade-in'; el.dataset.wid = id; el.style.cssText = `left:${left}px;top:${top}px;width:${width}px;height:${height}px;z-index:${nextZ()}`; el.innerHTML = `