Skip to content

Commit 78c5e97

Browse files
authored
[UI] Add freeform layout (#47)
With this layout selected one can drag and resize IR output windows.
1 parent bfcb43c commit 78c5e97

File tree

3 files changed

+786
-547
lines changed

3 files changed

+786
-547
lines changed

src/app/DraggableWindow.js

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"use client";
2+
3+
import React, { useRef } from "react";
4+
5+
/**
6+
* Lightweight draggable/resizable wrapper used when the react-rnd package
7+
* is unavailable. It supports dragging via an element with class "drag-title"
8+
* and exposes onDrag/onResize callbacks that receive updated geometry.
9+
*/
10+
export function DraggableWindow({
11+
id,
12+
zIndex,
13+
pos,
14+
size,
15+
collapsed,
16+
onFocus,
17+
onDrag,
18+
onResize,
19+
children,
20+
}) {
21+
const ref = useRef(null);
22+
const height = collapsed ? 40 : size.h;
23+
24+
const clamp = (value, min, max) => Math.max(min, Math.min(value, max));
25+
26+
const startDrag = (e) => {
27+
const handle = e.target.closest(".drag-title");
28+
if (!handle) return;
29+
onFocus && onFocus();
30+
const startX = e.clientX;
31+
const startY = e.clientY;
32+
const startPos = { ...pos };
33+
const parentRect = ref.current?.parentElement?.getBoundingClientRect();
34+
35+
const onMove = (ev) => {
36+
const dx = ev.clientX - startX;
37+
const dy = ev.clientY - startY;
38+
let x = startPos.x + dx;
39+
let y = startPos.y + dy;
40+
if (parentRect) {
41+
const maxX = parentRect.width - size.w;
42+
const maxY = parentRect.height - height;
43+
x = clamp(x, 0, maxX);
44+
y = clamp(y, 0, maxY);
45+
}
46+
onDrag({ x, y });
47+
};
48+
49+
const onUp = () => {
50+
document.removeEventListener("mousemove", onMove);
51+
document.removeEventListener("mouseup", onUp);
52+
};
53+
54+
document.addEventListener("mousemove", onMove);
55+
document.addEventListener("mouseup", onUp);
56+
};
57+
58+
const startResize = (e, dir) => {
59+
e.stopPropagation();
60+
onFocus && onFocus();
61+
const startX = e.clientX;
62+
const startY = e.clientY;
63+
const start = { w: size.w, h: size.h, x: pos.x, y: pos.y };
64+
const parentRect = ref.current?.parentElement?.getBoundingClientRect();
65+
66+
const onMove = (ev) => {
67+
const dx = ev.clientX - startX;
68+
const dy = ev.clientY - startY;
69+
let { w, h, x, y } = start;
70+
if (dir.includes("right")) w = start.w + dx;
71+
if (dir.includes("left")) {
72+
w = start.w - dx;
73+
x = start.x + dx;
74+
}
75+
if (dir.includes("bottom")) h = start.h + dy;
76+
if (dir.includes("top")) {
77+
h = start.h - dy;
78+
y = start.y + dy;
79+
}
80+
w = Math.max(320, w);
81+
h = Math.max(180, h);
82+
if (parentRect) {
83+
const maxX = parentRect.width - w;
84+
const maxY = parentRect.height - h;
85+
x = clamp(x, 0, maxX);
86+
y = clamp(y, 0, maxY);
87+
}
88+
onResize({ w, h }, { x, y });
89+
};
90+
91+
const onUp = () => {
92+
document.removeEventListener("mousemove", onMove);
93+
document.removeEventListener("mouseup", onUp);
94+
};
95+
96+
document.addEventListener("mousemove", onMove);
97+
document.addEventListener("mouseup", onUp);
98+
};
99+
100+
const handleStyles = collapsed
101+
? []
102+
: [
103+
{
104+
dir: "top",
105+
style: { top: -4, left: "50%", marginLeft: -4, cursor: "n-resize" },
106+
},
107+
{
108+
dir: "right",
109+
style: { right: -4, top: "50%", marginTop: -4, cursor: "e-resize" },
110+
},
111+
{
112+
dir: "bottom",
113+
style: {
114+
bottom: -4,
115+
left: "50%",
116+
marginLeft: -4,
117+
cursor: "s-resize",
118+
},
119+
},
120+
{
121+
dir: "left",
122+
style: { left: -4, top: "50%", marginTop: -4, cursor: "w-resize" },
123+
},
124+
{ dir: "top-left", style: { top: -4, left: -4, cursor: "nw-resize" } },
125+
{
126+
dir: "top-right",
127+
style: { top: -4, right: -4, cursor: "ne-resize" },
128+
},
129+
{
130+
dir: "bottom-left",
131+
style: { bottom: -4, left: -4, cursor: "sw-resize" },
132+
},
133+
{
134+
dir: "bottom-right",
135+
style: { bottom: -4, right: -4, cursor: "se-resize" },
136+
},
137+
];
138+
139+
return (
140+
<div
141+
ref={ref}
142+
onMouseDown={startDrag}
143+
style={{
144+
position: "absolute",
145+
left: pos.x,
146+
top: pos.y,
147+
width: size.w,
148+
height: height,
149+
zIndex,
150+
}}
151+
>
152+
{children}
153+
{handleStyles.map((h) => (
154+
<div
155+
key={h.dir}
156+
onMouseDown={(e) => startResize(e, h.dir)}
157+
style={{
158+
position: "absolute",
159+
width: 8,
160+
height: 8,
161+
background: "transparent",
162+
...h.style,
163+
}}
164+
/>
165+
))}
166+
</div>
167+
);
168+
}
169+
170+
export default DraggableWindow;

0 commit comments

Comments
 (0)