RevelaOS/src/wm.ts
2023-10-18 16:01:16 +01:00

242 lines
6.4 KiB
TypeScript

import { v4 as uuid } from 'uuid'
import { FlowWindowConfig } from './types.ts'
function dragElement (element: HTMLElement, container: HTMLElement): void {
let posX = 0; let posY = 0
element.querySelector('window-header')?.addEventListener('mousedown', dragMouseDown)
function dragMouseDown (e: MouseEvent): void {
e.preventDefault()
closeAll()
posX = e.clientX
posY = e.clientY
document.onmouseup = closeDragElement
document.onmousemove = elementDrag
}
function elementDrag (e: MouseEvent): void {
e.preventDefault()
const dx = e.clientX - posX
const dy = e.clientY - posY
const newTop = element.offsetTop + dy
const newLeft = element.offsetLeft + dx
const containerWidth = container.offsetWidth
const containerHeight = container.offsetHeight
if (newTop >= 0 && newTop + element.offsetHeight <= containerHeight) {
element.style.top = `${newTop}px`
}
if (newLeft >= 0 && newLeft + element.offsetWidth <= containerWidth) {
element.style.left = `${newLeft}px`
}
posX = e.clientX
posY = e.clientY
}
function closeDragElement (): void {
document.onmouseup = null
document.onmousemove = null
container.onmouseleave = null
}
function closeAll (): void {
closeDragElement()
container.onmouseenter = null
}
}
export class FlowWindow {
element: HTMLElement
private readonly header: HTMLElement
content: HTMLElement
maximized: boolean
minimized: boolean
width: number
height: number
isMinimized = false
isMaximized = false
wm: WM
id = uuid()
config: FlowWindowConfig
constructor (wm: WM, config: FlowWindowConfig) {
this.wm = wm
this.config = config
this.element = document.createElement('window')
this.element.style.zIndex = (wm.getHighestZIndex() + 1).toString()
this.element.style.position = 'absolute'
this.focus()
this.element.onmousedown = () => {
this.focus()
}
if (config.canResize === undefined) config.canResize = true
this.element.style.width = `${config.width ?? 300}px`
this.element.style.height = `${config.height ?? 200}px`
this.header = document.createElement('window-header')
this.header.innerHTML = `<img src="${config.icon}"></img> <div class="title">${config.title}</div><div style="flex:1;"></div><i id="min" class='bx bx-minus'></i><i id="close" class='bx bx-x'></i>`
if (!config.canResize) {
this.header.innerHTML = `<img src="${config.icon}"></img> <div class="title">${config.title}</div><div style="flex:1;"></div><i id="min" class='bx bx-minus'></i><i id="max" class='bx bx-checkbox'></i><i id="close" class='bx bx-x'></i>`
}
(this.header.querySelector('#close') as HTMLElement).onclick = () => {
this.close()
}
(this.header.querySelector('#min') as HTMLElement).onclick = () => this.toggleMin()
if (!config.canResize) {
(this.header.querySelector('#max') as HTMLElement).onclick = () => this.toggleMax()
}
this.content = document.createElement('window-content')
this.element.appendChild(this.header)
this.element.appendChild(this.content)
dragElement(this.element, (document.querySelector('window-area') as HTMLElement))
}
toggleMin (): boolean {
if (this.isMinimized) {
this.element.style.pointerEvents = 'all'
this.element.style.opacity = '1'
} else {
this.element.style.pointerEvents = 'none'
this.element.style.opacity = '0'
}
this.isMinimized = !this.isMinimized
return this.isMinimized
}
private prevTop: string
private prevLeft: string
private prevWidth: string
private prevHeight: string
toggleMax (): boolean {
if (this.isMaximized) {
this.element.style.width = this.prevWidth
this.element.style.height = this.prevHeight
this.element.style.top = this.prevTop
this.element.style.left = this.prevLeft
} else {
this.prevTop = this.element.style.top
this.prevLeft = this.element.style.left
this.prevWidth = this.element.style.width
this.prevHeight = this.element.style.height
this.element.style.top = '0'
this.element.style.left = '0'
this.element.style.width = 'calc(100% - 2px)'
this.element.style.height = 'calc(100% - 3px)'
}
this.isMaximized = !this.isMaximized
return this.isMaximized
}
focus (): void {
if (this.element.style.zIndex !== this.wm.getHighestZIndex().toString()) {
this.element.style.zIndex = (this.wm.getHighestZIndex() + 1).toString()
}
}
close (): void {
this.element.remove()
const event = new CustomEvent('app_closed', { detail: { win: this } })
window.dispatchEvent(event)
}
setTitle (title: string): void {
(this.header.querySelector('.title') as HTMLElement).innerText = title
}
}
class WM {
private isLauncherOpen = false
windowArea: HTMLElement
launcher: HTMLElement
windows: FlowWindow[] = []
constructor () {
this.windowArea = document.createElement('window-area')
}
getHighestZIndex (): number {
const indexes = this.windows.map((win: FlowWindow) => {
return parseInt(win.element.style.zIndex)
})
const max = Math.max(...indexes)
return max === -Infinity ? 0 : max
}
createWindow (config: FlowWindowConfig): FlowWindow {
const win = new FlowWindow(this, config)
this.windows.push(win)
this.windowArea.appendChild(win.element)
return win
}
toggleLauncher (): boolean {
if (this.isLauncherOpen) {
this.launcher.style.opacity = '0'
this.launcher.style.backdropFilter = 'blur(0px)'
this.launcher.style.pointerEvents = 'none'
} else {
this.launcher.style.opacity = '1'
this.launcher.style.backdropFilter = 'blur(20px)'
this.launcher.style.pointerEvents = 'all'
}
this.isLauncherOpen = !this.isLauncherOpen
return this.isLauncherOpen
}
async init (): Promise<void> {
window.preloader.setPending('window manager')
window.preloader.setStatus('creating app launcher...')
this.launcher = document.createElement('launcher')
this.launcher.innerHTML = `
<input placeholder="Search"/>
<apps></apps>
`
this.launcher.onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
(this.launcher.querySelector('apps') as HTMLElement).onclick = (e) => {
if (e.target !== e.currentTarget) return
this.toggleLauncher()
}
await window.preloader.setDone('window manager')
}
}
export default WM