feat: scene traps, material legends, and scene editor window
Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import type { MaterialLegend, MaterialsOverlayLayout } from '../../../shared/types';
|
||||
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT } from '../../../shared/types';
|
||||
import { useSceneOverlayView } from '../sceneOverlay/SceneOverlayViewContext';
|
||||
|
||||
import styles from './MaterialLegendPanel.module.css';
|
||||
|
||||
type Props = {
|
||||
legend: MaterialLegend;
|
||||
layout: MaterialsOverlayLayout;
|
||||
editable?: boolean;
|
||||
onLayoutChange?: (layout: MaterialsOverlayLayout) => void;
|
||||
};
|
||||
|
||||
export function MaterialLegendPanel({
|
||||
legend,
|
||||
layout,
|
||||
editable = false,
|
||||
onLayoutChange,
|
||||
}: Props) {
|
||||
const host = useSceneOverlayView();
|
||||
const view = host?.view ?? { w: 1, h: 1 };
|
||||
const dragRef = useRef<{ startX: number; startY: number; origin: MaterialsOverlayLayout } | null>(
|
||||
null,
|
||||
);
|
||||
const effective = layout ?? DEFAULT_MATERIALS_OVERLAY_LAYOUT;
|
||||
const w = Math.max(180, Math.min(view.w * 0.36, 340) * effective.scale);
|
||||
const left = effective.cx * view.w - w / 2;
|
||||
const top = effective.cy * view.h - 40;
|
||||
|
||||
if (!legend.enabled || legend.items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.panel}
|
||||
style={{ left, top, width: w }}
|
||||
onPointerDown={(e) => {
|
||||
if (!editable || !onLayoutChange) return;
|
||||
e.stopPropagation();
|
||||
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);
|
||||
dragRef.current = {
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
origin: { ...effective },
|
||||
};
|
||||
}}
|
||||
onPointerMove={(e) => {
|
||||
const d = dragRef.current;
|
||||
if (!d || !onLayoutChange) return;
|
||||
const dx = (e.clientX - d.startX) / Math.max(1, view.w);
|
||||
const dy = (e.clientY - d.startY) / Math.max(1, view.h);
|
||||
onLayoutChange({
|
||||
...d.origin,
|
||||
cx: d.origin.cx + dx,
|
||||
cy: d.origin.cy + dy,
|
||||
});
|
||||
}}
|
||||
onPointerUp={() => {
|
||||
dragRef.current = null;
|
||||
}}
|
||||
>
|
||||
<div className={styles.title}>Легенда</div>
|
||||
{legend.items
|
||||
.slice()
|
||||
.sort((a, b) => a.number - b.number)
|
||||
.map((item) => (
|
||||
<div key={item.id} className={styles.item}>
|
||||
<div className={styles.num}>{item.number}</div>
|
||||
<div className={styles.text}>{item.text || '—'}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user