02d73ddf81
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>
147 lines
4.1 KiB
TypeScript
147 lines
4.1 KiB
TypeScript
import {
|
|
clampMaterialsLayout,
|
|
DEFAULT_MATERIALS_OVERLAY_LAYOUT,
|
|
type MaterialId,
|
|
type MaterialsOverlayEvent,
|
|
type MaterialsOverlayLayout,
|
|
type MaterialsOverlayState,
|
|
type MaterialsZoomTool,
|
|
zoomMaterialsLayoutAt,
|
|
} from '../../shared/types';
|
|
|
|
function emptyState(): MaterialsOverlayState {
|
|
return {
|
|
revision: 1,
|
|
activeMaterialId: null,
|
|
layout: { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT },
|
|
legendLayout: { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT, cx: 0.82, cy: 0.5, scale: 0.85 },
|
|
zoomTool: null,
|
|
};
|
|
}
|
|
|
|
function initialLayout(rotationDeg?: number): MaterialsOverlayLayout {
|
|
return clampMaterialsLayout({
|
|
...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
|
|
rotationDeg: rotationDeg ?? 0,
|
|
});
|
|
}
|
|
|
|
function initialLegendLayout(): MaterialsOverlayLayout {
|
|
return clampMaterialsLayout({
|
|
...DEFAULT_MATERIALS_OVERLAY_LAYOUT,
|
|
cx: 0.82,
|
|
cy: 0.5,
|
|
scale: 0.85,
|
|
rotationDeg: 0,
|
|
});
|
|
}
|
|
|
|
export class MaterialsOverlayStore {
|
|
private state: MaterialsOverlayState = emptyState();
|
|
|
|
getState(): MaterialsOverlayState {
|
|
return this.state;
|
|
}
|
|
|
|
clear(): MaterialsOverlayState {
|
|
if (this.state.activeMaterialId === null && this.state.zoomTool === null) {
|
|
return this.state;
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialId: null,
|
|
layout: { ...DEFAULT_MATERIALS_OVERLAY_LAYOUT },
|
|
legendLayout: initialLegendLayout(),
|
|
zoomTool: null,
|
|
};
|
|
return this.state;
|
|
}
|
|
|
|
dispatch(event: MaterialsOverlayEvent): MaterialsOverlayState {
|
|
switch (event.kind) {
|
|
case 'hide':
|
|
return this.clear();
|
|
case 'show':
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialId: event.materialId,
|
|
layout: initialLayout(event.rotationDeg),
|
|
legendLayout: initialLegendLayout(),
|
|
zoomTool: this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
case 'toggle': {
|
|
if (this.state.activeMaterialId === event.materialId) {
|
|
return this.clear();
|
|
}
|
|
this.state = {
|
|
revision: this.state.revision + 1,
|
|
activeMaterialId: event.materialId,
|
|
layout: initialLayout(event.rotationDeg),
|
|
legendLayout: initialLegendLayout(),
|
|
zoomTool: this.state.zoomTool,
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'layout.set': {
|
|
if (this.state.activeMaterialId === null) return this.state;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
layout: clampMaterialsLayout({
|
|
...this.state.layout,
|
|
...event.layout,
|
|
}),
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'legendLayout.set': {
|
|
if (this.state.activeMaterialId === null) return this.state;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
legendLayout: clampMaterialsLayout({
|
|
...this.state.legendLayout,
|
|
...event.layout,
|
|
rotationDeg: 0,
|
|
}),
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'zoomTool.set': {
|
|
const tool: MaterialsZoomTool = event.tool;
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
zoomTool: tool,
|
|
};
|
|
return this.state;
|
|
}
|
|
case 'zoomAt': {
|
|
if (this.state.activeMaterialId === null || !this.state.zoomTool) return this.state;
|
|
const factor = this.state.zoomTool === 'zoomIn' ? 1.25 : 1 / 1.25;
|
|
const layout: MaterialsOverlayLayout = zoomMaterialsLayoutAt(
|
|
this.state.layout,
|
|
event.nx,
|
|
event.ny,
|
|
factor,
|
|
);
|
|
this.state = {
|
|
...this.state,
|
|
revision: this.state.revision + 1,
|
|
layout,
|
|
};
|
|
return this.state;
|
|
}
|
|
default:
|
|
return this.state;
|
|
}
|
|
}
|
|
|
|
ensureMaterialStillExists(materialIds: ReadonlySet<MaterialId>): MaterialsOverlayState {
|
|
const active = this.state.activeMaterialId;
|
|
if (active === null || materialIds.has(active)) return this.state;
|
|
return this.clear();
|
|
}
|
|
}
|