Files
os-gui/script.js

303 lines
9.2 KiB
JavaScript
Raw Normal View History

2025-08-09 05:25:35 +02:00
// 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');
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);
}