176 lines
4.3 KiB
JavaScript
176 lines
4.3 KiB
JavaScript
/*
|
|
The WindowManager is responsible for creation, destruction, modification and tracking of game UI windows.
|
|
*/
|
|
|
|
class WindowManager
|
|
{
|
|
constructor(container)
|
|
{
|
|
this.windows = {}
|
|
this.container = container
|
|
}
|
|
|
|
updateWindow(id, content, title = '')
|
|
{
|
|
if (id in this.windows) {
|
|
let w = this.windows[id]
|
|
w.querySelector('header .title').innerHTML = title
|
|
w.querySelector('.body').innerHTML = content
|
|
this.bringToFront(w)
|
|
return
|
|
}
|
|
|
|
let w = this.createWindow(id, content, title)
|
|
this.bringToFront(w)
|
|
}
|
|
|
|
createWindow(id, content, title = '')
|
|
{
|
|
// create window
|
|
let w = document.createElement('div')
|
|
w.id = `window-${id}`
|
|
w.classList.add('window')
|
|
|
|
// create header
|
|
let h = document.createElement('header')
|
|
w.appendChild(h)
|
|
|
|
// create header title
|
|
let ht = document.createElement('span')
|
|
ht.classList.add('title')
|
|
ht.innerHTML = title
|
|
h.appendChild(ht)
|
|
|
|
// create close button
|
|
ht.insertAdjacentHTML('afterend', '<a class="close"><img src="/assets/img/ui/icons/bullet_red.png"></a>')
|
|
h.querySelector('a.close').addEventListener('click', () => {
|
|
this.windows[id].remove()
|
|
delete this.windows[id]
|
|
})
|
|
|
|
// create body
|
|
let b = document.createElement('div')
|
|
b.classList.add('body')
|
|
b.innerHTML = content
|
|
w.appendChild(b)
|
|
|
|
// track window and add it to the container
|
|
this.makeWindowDraggable(w, this.container)
|
|
this.windows[id] = w
|
|
this.container.appendChild(w)
|
|
return w
|
|
}
|
|
|
|
makeWindowDraggable(w, c)
|
|
{
|
|
const header = w.querySelector('header');
|
|
if (!header) return;
|
|
|
|
let isDragging = false;
|
|
let currentX;
|
|
let currentY;
|
|
let initialX;
|
|
let initialY;
|
|
|
|
header.addEventListener('mousedown', startDragging);
|
|
document.addEventListener('mousemove', drag);
|
|
document.addEventListener('mouseup', stopDragging);
|
|
|
|
w.addEventListener('mousedown', () => this.bringToFront(w));
|
|
|
|
function startDragging(e)
|
|
{
|
|
isDragging = true;
|
|
initialX = e.clientX - w.offsetLeft;
|
|
initialY = e.clientY - w.offsetTop;
|
|
}
|
|
|
|
function drag(e)
|
|
{
|
|
if (!isDragging) return;
|
|
|
|
e.preventDefault();
|
|
|
|
// Calculate new position
|
|
currentX = e.clientX - initialX;
|
|
currentY = e.clientY - initialY;
|
|
|
|
// Get viewport dimensions
|
|
const viewportWidth = window.innerWidth;
|
|
const viewportHeight = window.innerHeight;
|
|
|
|
// Get window dimensions
|
|
const windowRect = w.getBoundingClientRect();
|
|
|
|
// Constrain to viewport bounds
|
|
// Left edge
|
|
currentX = Math.max(0, currentX);
|
|
// Right edge
|
|
currentX = Math.min(viewportWidth - windowRect.width, currentX);
|
|
// Top edge
|
|
currentY = Math.max(0, currentY);
|
|
// Bottom edge
|
|
currentY = Math.min(viewportHeight - windowRect.height, currentY);
|
|
|
|
// Apply the constrained position
|
|
w.style.left = currentX + 'px';
|
|
w.style.top = currentY + 'px';
|
|
}
|
|
|
|
function stopDragging()
|
|
{
|
|
isDragging = false;
|
|
}
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', () => {
|
|
// Get current position and dimensions
|
|
const rect = w.getBoundingClientRect();
|
|
const viewportWidth = window.innerWidth;
|
|
const viewportHeight = window.innerHeight;
|
|
|
|
// Adjust position if window is outside viewport after resize
|
|
let newX = parseInt(w.style.left);
|
|
let newY = parseInt(w.style.top);
|
|
|
|
// Constrain to new viewport bounds
|
|
newX = Math.min(Math.max(0, newX), viewportWidth - rect.width);
|
|
newY = Math.min(Math.max(0, newY), viewportHeight - rect.height);
|
|
|
|
w.style.left = newX + 'px';
|
|
w.style.top = newY + 'px';
|
|
});
|
|
}
|
|
|
|
normalizeZIndices()
|
|
{
|
|
const array = Array.from(this.windows);
|
|
array.sort((a, b) => (parseInt(a.style.zIndex) || 0) - (parseInt(b.style.zIndex) || 0));
|
|
|
|
// Reassign z-indices starting from 1
|
|
array.forEach((win, index) => {
|
|
win.style.zIndex = index + 1;
|
|
});
|
|
|
|
return array.length + 1; // Return next available z-index
|
|
}
|
|
|
|
bringToFront(windowElement)
|
|
{
|
|
const currentMax = Math.max(...Object.values(this.windows)
|
|
.map(w => parseInt(w.style.zIndex) || 0));
|
|
|
|
if (parseInt(windowElement.style.zIndex) >= currentMax) {
|
|
return
|
|
}
|
|
|
|
// If z-index is getting too high, normalize all z-indices
|
|
if (currentMax > 10000) {
|
|
const newZ = this.normalizeZIndices();
|
|
windowElement.style.zIndex = newZ;
|
|
} else {
|
|
windowElement.style.zIndex = currentMax + 1;
|
|
}
|
|
}
|
|
}
|