1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
import { ComponentChildren, Ref, RefObject } from "preact";
import { useCallback, useRef, useState } from "preact/hooks";
export function Window(props: Window.Props) {
const windowRef: Ref<HTMLDivElement> = useRef(null);
const dragTimeout: Ref<ReturnType<typeof setTimeout>> = useRef(null);
const [x, setX] = useState(props.x ?? 0);
const [y, setY] = useState(props.y ?? 0);
const initialOffsetX = useRef(0);
const initialOffsetY = useRef(0);
const updatePosition = useCallback((e: MouseEvent) => {
setX(e.clientX - initialOffsetX.current)
setY(e.clientY - initialOffsetY.current)
}, [])
const startDrag = useCallback((e: MouseEvent) => {
if (e.button === 0) {
const BORDER_WIDTH = 1;
console.log(e);
// Naturally, I have to add the border width here, because offset* uses padding-edge for the calculations
initialOffsetX.current = e.offsetX + BORDER_WIDTH
initialOffsetY.current = e.offsetY + BORDER_WIDTH
windowRef.current?.classList.add("dragging")
if (dragTimeout.current !== null) {
clearTimeout(dragTimeout.current);
dragTimeout.current = null;
} else {
props.desktop.current?.addEventListener("mousemove", updatePosition)
props.desktop.current?.addEventListener("mouseleave", stopDrag)
}
}
}, []);
const stopDrag = useCallback((e: MouseEvent) => {
if (e.button === 0) {
dragTimeout.current = setTimeout(() => {
dragTimeout.current = null;
windowRef.current?.classList.remove("dragging")
props.desktop.current?.removeEventListener("mousemove", updatePosition)
props.desktop.current?.removeEventListener("mouseleave", stopDrag)
}, 50) // intrdouce a small delay where the user can still drag the window without actually pressing down lmb to prevent accidental drops
}
}, []);
return <div class="window" ref={windowRef} style={{ width: props.width, height: props.height, left: x, top: y}}>
<div class="titlebar" onPointerDown={startDrag} onPointerUp={stopDrag}>
<div class="title">{props.title}</div>
<div class="buttons" onPointerDown={e => e.stopPropagation()}>
<button>
<img src="/assets/minimize.png" />
</button>
<button>
<img src="/assets/maximize.png" />
</button>
<button>
<img src="/assets/close.png" />
</button>
</div>
</div>
<div class="content">
{props.children}
</div>
</div>
}
namespace Window {
export type Props = {
title: string;
width?: string | number;
height?: string | number;
x?: string | number;
y?: string | number;
desktop: RefObject<HTMLDivElement>;
children: ComponentChildren
}
}
|