// WINDOW MANAGER const desktop = document.getElementById('desktop'); const bglayer = document.getElementById('background-layer'); const newWindowBtn = document.getElementById('new-window-btn'); let zIndexCounter = 1; let focusedWindow = null; let windows = {}; let windowHooks = []; function uniqueid() { // always start with a letter (for DOM friendlyness) var idstr = String.fromCharCode(Math.floor((Math.random() * 25) + 65)); do { // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90) var ascicode = Math.floor((Math.random() * 42) + 48); if (ascicode < 58 || ascicode > 64) { // exclude all chars between : (58) and @ (64) idstr += String.fromCharCode(ascicode); } } while (idstr.length < 32); return (idstr); } function fadeOut(fadeTarget, speed, callback) { let eee = 0; var fadeEffect = setInterval(function () { if (!fadeTarget.style.opacity) { fadeTarget.style.opacity = 1; } fadeTarget.style.transform = `rotate3d(1, 0, 0, ${eee}deg)`; if (fadeTarget.style.opacity > 0) { fadeTarget.style.opacity -= 0.1; eee += 10; } else { clearInterval(fadeEffect); callback(); } }, speed); } function killWind(id) { const windowDiv = windows[id]?.windElems?.windowDiv; if (typeof (windowDiv) == "undefined") { console.warn("Attempted to close undefined window.."); return; }; fadeOut(windowDiv, 20, function () { windowDiv.remove(); delete windows[id]; windowHooks.forEach(element => { element({ event: "windowDestroy", id: id }) }); }); } function focusWindow(id) { const windData = windows[id]; const windowDiv = windData?.windElems?.windowDiv; if (typeof (windData) == "undefined") { console.warn("Attempted to focus undefined window.."); return; }; windowDiv.style.zIndex = zIndexCounter++; if (windData.layer == "background") windowDiv.style.zIndex = windowDiv.style.zIndex * -1; windowHooks.forEach(element => { element({ event: "windowFocused", id: id }) }); Object.keys(windows).forEach(key => { const element = windows[key]; if (element.alwaysOnTop) { element.windElems.windowDiv.style.zIndex = zIndexCounter + 5; } }); focusedWindow = id; } function createWindow(url, settings) { const windID = uniqueid(); const name = settings.name; const headerless = settings.headerless || false; const resizable = settings.resizable !== undefined ? settings.resizable : true; const shouldIndent = settings.indent !== undefined ? settings.indent : true; const layer = settings.layer || "foreground"; const windowDiv = document.createElement('div'); windowDiv.classList.add('window'); if (headerless) windowDiv.classList.add('window-noborder'); if (shouldIndent) windowDiv.style.top = '50px'; if (shouldIndent) windowDiv.style.left = '50px'; windowDiv.style.zIndex = zIndexCounter++; windowDiv.id = windID; // Header const header = document.createElement('div'); header.classList.add('window-header'); if (headerless) header.classList.add("window-hidden"); const title = document.createElement('div'); title.classList.add('window-title'); title.textContent = name || url; const closeBtn = document.createElement('button'); closeBtn.classList.add('window-close-btn'); closeBtn.textContent = '×'; header.appendChild(title); header.appendChild(closeBtn); windowDiv.appendChild(header); // iframe const iframe = document.createElement('iframe'); iframe.classList.add('window-iframe'); iframe.src = url; windowDiv.appendChild(iframe); iframe.onload = function () { try { iframe.contentDocument.oncontextmenu = function () { return false; }; } catch { }; }; // Add resize handle if resizable let resizeHandle = null; if (resizable) { resizeHandle = document.createElement('div'); resizeHandle.style.width = '16px'; resizeHandle.style.height = '16px'; resizeHandle.style.position = 'absolute'; resizeHandle.style.right = '0'; resizeHandle.style.bottom = '0'; resizeHandle.style.cursor = 'se-resize'; // Optional: make it visible if you want // resizeHandle.style.background = 'rgba(0,0,0,0.2)'; windowDiv.appendChild(resizeHandle); } if (layer == "foreground") desktop.appendChild(windowDiv); else bglayer.appendChild(windowDiv); // Dragging functionality let isDragging = false; let offsetX, offsetY; header.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - windowDiv.getBoundingClientRect().left; offsetY = e.clientY - windowDiv.getBoundingClientRect().top; focusWindow(windID); }); // Resizing variables let isResizing = false; let startWidth, startHeight, startX, startY; window.addEventListener('mouseup', () => { isDragging = false; isResizing = false; }); window.addEventListener('mousemove', (e) => { if (isDragging) { let newX = e.clientX - offsetX; let newY = e.clientY - offsetY; transformWindow(windID, null, null, newX, newY); } else if (isResizing) { const dx = e.clientX - startX; const dy = e.clientY - startY; const newWidth = startWidth + dx; const newHeight = startHeight + dy; transformWindow(windID, newWidth, newHeight); } }); // Resize handle mousedown if (resizeHandle) { resizeHandle.addEventListener('mousedown', (e) => { e.stopPropagation(); isResizing = true; startWidth = windowDiv.offsetWidth; startHeight = windowDiv.offsetHeight; startX = e.clientX; startY = e.clientY; focusWindow(windID); }); } // Focus window on click windowDiv.addEventListener('mousedown', () => { focusWindow(windID); }); // Close window closeBtn.addEventListener('click', () => { killWind(windID); }); const windObj = { iframe: iframe, windElems: { closeBtn: closeBtn, windowDiv: windowDiv, header: header, title: title, resizeHandle: resizeHandle }, windID: windID, minSizeX: 250, minSizeY: 150, alwaysOnTop: false, layer: layer, hideInTaskbar: layer == "background" }; windows[windID] = windObj; windowHooks.forEach(element => { element({ event: "windowCreate", id: windID, window: windObj }) }); return windObj; } function transformWindow(id, sizex, sizey, posx, posy, ignoreRects) { const windData = windows[id]; const windowDiv = windData?.windElems?.windowDiv; if (typeof (windData) == "undefined") { console.warn("Attempted to transform undefined window.."); return; }; if (sizex) windowDiv.style.width = Math.max(windData.minSizeX, sizex) + 'px'; if (sizey) windowDiv.style.height = Math.max(windData.minSizeY, sizey) + 'px'; const desktopRect = desktop.getBoundingClientRect(); const winRect = windowDiv.getBoundingClientRect(); if (posx) { const newX = ignoreRects ? posx : Math.max(desktopRect.left, Math.min(posx, desktopRect.right - winRect.width)); windowDiv.style.left = newX - desktopRect.left + 'px'; } if (posy) { const newY = ignoreRects ? posy : Math.max(desktopRect.top, Math.min(posy, desktopRect.bottom - winRect.height)); windowDiv.style.top = newY - desktopRect.top + 'px'; } } function getTransform(id) { const windData = windows[id]; const windowDiv = windData?.windElems?.windowDiv; if (typeof windData === "undefined" || !windowDiv) { console.warn("Attempted to transform undefined window.."); return; } const desktopRect = desktop.getBoundingClientRect(); // Extract numeric values from style or fallback to computed styles const style = windowDiv.style; // Helper to parse px values safely function parsePx(val) { if (!val) return 0; return Math.floor(parseFloat(val)); } // Use inline style if present, otherwise computed style let topStr = style.top || window.getComputedStyle(windowDiv).top; let leftStr = style.left || window.getComputedStyle(windowDiv).left; let widthStr = style.width || window.getComputedStyle(windowDiv).width; let heightStr = style.height || window.getComputedStyle(windowDiv).height; // Parse numbers from strings like "50px" const posy = parsePx(topStr); const posx = parsePx(leftStr); const sizex = parsePx(widthStr); const sizey = parsePx(heightStr); return { x: posx, y: posy, sizex: sizex, sizey: sizey }; } function updateWindowMin(id) { const windData = windows[id]; if (typeof (windData) == "undefined") { console.warn("Attempted to update undefined window.."); return; }; const transform = getTransform(id); transformWindow(id,transform.sizex,transform.sizey,transform.posx,transform.posy); }