Files
feuerwerk/src/editor/inspector/Inspector.ts
T

89 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Container } from 'pixi.js';
import { Label, Panel, ScrollContainer, Theme, ui, VStack } from '../../ui';
import { type EditorState, sameSelection, type Selection } from '../state';
import { assetSignature, buildAssetSection, refreshAssetSection } from './assetSection';
import type { InspectorHost } from './host';
import { buildNodeSection, nodeSignature, refreshNodeSection } from './nodeSection';
import { buildObjectSection, buildObjectsSection, objectSignature, refreshObjectSection } from './objectSection';
/**
* Shows the properties of the current selection. The rows are generated from the
* PropertySchema by one section per selection kind (asset, scene object, graph node); the
* panel only decides when a section has to be rebuilt and when its values merely refresh.
*/
export class Inspector extends Panel {
private readonly scroll = new ScrollContainer();
private host: InspectorHost;
private builtFor: Selection = null;
/** Describes the structure the rows were built for; a change means a rebuild. */
private signature = '';
/** Last live refresh (play mode), in ms – the world ticks far more often than the eye needs. */
private lastLiveRefresh = 0;
constructor(private readonly state: EditorState) {
super({ title: 'Inspector', padding: 0, gap: 0 });
this.host = this.emptyHost();
this.scroll.setContent(this.host.body);
this.content.add(this.scroll, 1);
state.on('selection', () => this.rebuild());
state.on('load', () => this.rebuild());
state.on('project', () => this.refreshValues());
state.on('runtime', () => this.refreshLive());
this.rebuild();
}
private emptyHost(): InspectorHost {
return { state: this.state, body: new VStack({ padding: Theme.padding, gap: Theme.gap }), fields: new Map(), nameField: null };
}
/**
* Play mode: show what the behaviors are doing to the selected object, ~10× a second.
* Skipped while a field in here is being edited so the typed text is not overwritten.
*/
private refreshLive(): void {
const now = performance.now();
if (now - this.lastLiveRefresh < 100) return;
for (let node: Container | null = ui().focus.focused; node; node = node.parent) if (node === this) return;
this.lastLiveRefresh = now;
this.refreshValues();
}
private rebuild(): void {
this.builtFor = this.state.selection;
this.host.body.destroy({ children: true });
this.host = this.emptyHost();
this.signature = this.currentSignature();
const s = this.state.selection;
if (!s) this.host.body.add(new Label('Select an asset, a scene object or a node.', { style: 'muted' }));
else if (s.kind === 'asset') buildAssetSection(this.host, s.assetId);
else if (s.kind === 'node') buildNodeSection(this.host, s.assetId, s.nodeId);
else if (s.objectIds.length > 1) buildObjectsSection(this.host, s.sceneId, s.objectIds);
else buildObjectSection(this.host, s.sceneId, s.objectId);
this.scroll.setContent(this.host.body);
}
private currentSignature(): string {
const s = this.state.selection;
if (!s) return '';
if (s.kind === 'asset') return assetSignature(this.state.project, s.assetId);
if (s.kind === 'node') return nodeSignature(this.state.project, s.assetId, s.nodeId);
return objectSignature(this.state.project, s.sceneId, s.objectIds, s.objectId);
}
/** Pushes current model values into the widgets (after undo, drag in viewport, …). */
private refreshValues(): void {
const s = this.state.selection;
if (!sameSelection(s, this.builtFor) || this.currentSignature() !== this.signature) {
this.rebuild();
return;
}
if (!s) return;
const ok =
s.kind === 'asset' ? refreshAssetSection(this.host, s.assetId)
: s.kind === 'node' ? refreshNodeSection(this.host, s.assetId, s.nodeId)
: s.objectIds.length > 1 || refreshObjectSection(this.host, s.sceneId, s.objectId);
if (!ok) this.rebuild();
}
}