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:
Ivan Fontosh
2026-07-24 16:29:43 +08:00
parent d3b1c4660d
commit 02d73ddf81
39 changed files with 2563 additions and 36 deletions
+37
View File
@@ -27,12 +27,15 @@ import { SceneDarknessOverlay } from '../shared/effects/SceneDarknessOverlay';
import { useEffectsState } from '../shared/effects/useEffectsState';
import { useSceneDarknessState } from '../shared/effects/useSceneDarknessState';
import { DEFAULT_MATERIALS_OVERLAY_LAYOUT, DEFAULT_NPCS_OVERLAY_LAYOUT } from '../../shared/types';
import { MaterialLegendPanel } from '../shared/materials/MaterialLegendPanel';
import { MaterialOverlay } from '../shared/materials/MaterialOverlay';
import { useMaterialsOverlayState } from '../shared/materials/useMaterialsOverlayState';
import { NpcsSceneOverlay } from '../shared/npcs/NpcsSceneOverlay';
import { useNpcsOverlayState } from '../shared/npcs/useNpcsOverlayState';
import { SceneOverlayHost } from '../shared/sceneOverlay/SceneOverlayHost';
import { useSceneViewState } from '../shared/sceneView/useSceneViewState';
import { SceneTrapsOverlay } from '../shared/traps/SceneTrapsOverlay';
import { useSceneTrapsState } from '../shared/traps/useSceneTrapsState';
import { Button } from '../shared/ui/controls';
import { Surface } from '../shared/ui/Surface';
import { useAssetUrl } from '../shared/useAssetImageUrl';
@@ -119,6 +122,7 @@ export function ControlApp() {
const [fxState, fx] = useEffectsState();
const [effectsSfxGainUi, setEffectsSfxGainUi] = useState(() => getEffectsSfxGain());
const [sdState, sd] = useSceneDarknessState();
const [sceneTraps, sceneTrapsApi] = useSceneTrapsState();
const [sceneView, sceneViewApi] = useSceneViewState();
const [sceneViewDraft, setSceneViewDraft] = useState<SceneViewCamera | null>(null);
const [materialsOverlay, materialsApi] = useMaterialsOverlayState();
@@ -1818,6 +1822,17 @@ export function ControlApp() {
clearDraftFromPixi();
}}
/>
{previewContentRect ? (
<SceneTrapsOverlay
traps={currentScene?.traps ?? []}
session={sceneTraps}
viewport={previewContentRect}
mode="control"
onReveal={(trapId) => void sceneTrapsApi.dispatch({ kind: 'reveal', trapId })}
onActivate={(trapId) => void sceneTrapsApi.dispatch({ kind: 'activate', trapId })}
onDisarm={(trapId) => void sceneTrapsApi.dispatch({ kind: 'disarm', trapId })}
/>
) : null}
</>
) : null}
{(() => {
@@ -1893,6 +1908,28 @@ export function ControlApp() {
onLayoutChange={(layout) => {
void materialsApi.dispatch({ kind: 'layout.set', layout });
}}
legendMarkers={
activeMaterial.legend?.enabled
? (activeMaterial.legend.markers ?? [])
: undefined
}
/>
) : null}
{showMaterial && activeMaterial?.legend?.enabled ? (
<MaterialLegendPanel
legend={activeMaterial.legend}
layout={
materialsOverlay?.legendLayout ?? {
...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
cx: 0.82,
cy: 0.5,
scale: 0.85,
}
}
editable
onLayoutChange={(layout) => {
void materialsApi.dispatch({ kind: 'legendLayout.set', layout });
}}
/>
) : null}
{showNpcs ? (