diff --git a/src/App.tsx b/src/App.tsx index 2194942..b2a477c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3082,8 +3082,11 @@ export default function App() { onSnapChange={setSnap} /> - {/* Schwebendes Ressourcen-Fenster ÜBER allem (hoher z-index, abgedunkelter - Hintergrund). Schließt über X / Klick auf den Hintergrund / Esc. */} + {/* Frei schwebendes, NICHT-modales Ressourcen-Fenster: kein Backdrop, der + Grundriss dahinter bleibt bedienbar (Schraffur-/Linien-Änderungen live + am Modell sichtbar). Verschiebbar über die Kopfzeile, größenveränderbar + über den Greifer unten rechts. Der „Ressourcen"-Knopf toggelt; Esc + schließt (siehe Effekt oben). */} {resourcesOpen && ( []) => void; } +// ── Schwebendes Fenster (nicht-modal) ────────────────────────────────────── +// Der Ressourcen-Manager schwebt frei über dem Grundriss, OHNE abdunkelnden +// Hintergrund: der Plan bleibt bedienbar, damit Schraffur-/Linien-Änderungen +// live am Modell sichtbar sind. Position/Größe leben lokal im Fenster; die +// Kopfzeile (res-head) ist der Verschiebegriff, unten rechts sitzt ein +// Größen-Greifer — beide über POINTER-Events mit Pointer-Capture (dieselbe +// Technik wie FloatingPanel), damit die Geste auch außerhalb weiterläuft. + +/** Standard- und Mindestgröße des schwebenden Ressourcen-Fensters (CSS-Pixel). */ +const RES_DEFAULT_W = 760; +const RES_DEFAULT_H = 640; +const RES_MIN_W = 420; +const RES_MIN_H = 320; + +/** Rechteck eines frei positionierten Fensters. */ +interface WindowRect { + x: number; + y: number; + w: number; + h: number; +} + +/** Sinnvolle Startlage: rechts oben unter der Oberleiste, in den Viewport geklemmt. */ +function initialRect(): WindowRect { + const vw = typeof window !== "undefined" ? window.innerWidth : 1280; + const vh = typeof window !== "undefined" ? window.innerHeight : 800; + const w = Math.min(RES_DEFAULT_W, Math.round(vw * 0.9)); + const h = Math.min(RES_DEFAULT_H, Math.round(vh * 0.85)); + const x = Math.max(16, vw - w - 24); + const y = 64; + return { x, y, w, h }; +} + /** - * Rechte Schublade mit drei Tabs (Bauteile / Schraffuren / Linien). Wird über - * den „Ressourcen"-Eintrag der Oberleiste geöffnet und mutiert das Projekt - * live über die gereichten Handler. + * Rechte Schublade mit mehreren Tabs (Bauteile / Schraffuren / Linien / …). Wird + * über den „Ressourcen"-Eintrag der Oberleiste geöffnet und mutiert das Projekt + * live über die gereichten Handler. Rendert als FREI SCHWEBENDES, nicht-modales + * Fenster (kein Backdrop) — verschiebbar über die Kopfzeile, größenveränderbar + * über den Greifer unten rechts. */ export function ResourceManager({ project, @@ -1932,16 +1968,75 @@ export function ResourceManager({ handlers: ResourceManagerHandlers; }) { const [tab, setTab] = useState("components"); + const [rect, setRect] = useState(initialRect); + + // Verschieben über die Kopfzeile: Cursor − linke/obere Kante festhalten und die + // Kante der Cursorbewegung folgen lassen (Pointer-Capture hält die Geste). + const onTitlePointerDown = (e: ReactPointerEvent) => { + // Klicks auf Knöpfe (Schließen) nicht als Verschiebe-Start werten. + if ((e.target as HTMLElement).closest("button")) return; + e.preventDefault(); + const handle = e.currentTarget; + handle.setPointerCapture(e.pointerId); + const dx = e.clientX - rect.x; + const dy = e.clientY - rect.y; + const move = (ev: PointerEvent) => { + setRect((r) => ({ ...r, x: ev.clientX - dx, y: ev.clientY - dy })); + }; + const up = (ev: PointerEvent) => { + handle.removeEventListener("pointermove", move); + handle.removeEventListener("pointerup", up); + handle.removeEventListener("pointercancel", up); + if (handle.hasPointerCapture(ev.pointerId)) { + handle.releasePointerCapture(ev.pointerId); + } + }; + handle.addEventListener("pointermove", move); + handle.addEventListener("pointerup", up); + handle.addEventListener("pointercancel", up); + }; + + // Größe ändern über den Greifer unten rechts (Deltas ab Startpunkt, Mindestmaß). + const onResizePointerDown = (e: ReactPointerEvent) => { + e.preventDefault(); + e.stopPropagation(); + const handle = e.currentTarget; + handle.setPointerCapture(e.pointerId); + const startX = e.clientX; + const startY = e.clientY; + const startW = rect.w; + const startH = rect.h; + const move = (ev: PointerEvent) => { + const w = Math.max(RES_MIN_W, Math.round(startW + (ev.clientX - startX))); + const h = Math.max(RES_MIN_H, Math.round(startH + (ev.clientY - startY))); + setRect((r) => ({ ...r, w, h })); + }; + const up = (ev: PointerEvent) => { + handle.removeEventListener("pointermove", move); + handle.removeEventListener("pointerup", up); + handle.removeEventListener("pointercancel", up); + if (handle.hasPointerCapture(ev.pointerId)) { + handle.releasePointerCapture(ev.pointerId); + } + }; + handle.addEventListener("pointermove", move); + handle.addEventListener("pointerup", up); + handle.addEventListener("pointercancel", up); + }; return ( -
-
); }