Files
feuerwerk/src/editor/inspector/fieldFactory.ts
T
rootandClaude Fable 5.1 5a46124454 Initial commit: Game Maker phase 1
Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript.

- core: asset model (Scene/Camera/Sprite), scene objects with sparse
  overrides, property schema + resolve, command stack (undo/redo),
  serialization and project store interface (16 unit tests)
- ui: custom in-canvas widget toolkit (panels, fields, color picker,
  list/scroll, popup) with a hidden <input> keyboard bridge
- editor: asset/hierarchy panels, schema-driven inspector, toolbar
- viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan
- runtime: world/entity instantiation for play mode, input, camera follow
- api: PHP JSON project store with revision counter (409 on conflict)
- tools: CDP-based e2e smoke test

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 10:07:06 +02:00

88 lines
2.8 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 { PropertyValue } from '../../core/model/Properties';
import type { PropertyDef } from '../../core/properties/PropertySchema';
import { Button, Checkbox, ColorField, HStack, Label, NumberField, TextField, Theme, type Widget } from '../../ui';
export interface FieldHandlers {
/** Live value while dragging/typing – apply as a preview, no undo entry. */
onInput: (value: PropertyValue) => void;
/** Final value – commit as a command. */
onChange: (value: PropertyValue) => void;
}
export interface FieldRow {
row: Widget;
/** Push a model value into the widget (no events fired). */
setValue(value: PropertyValue): void;
/** Highlight the row as overridden (scene objects) and show the reset button. */
setOverridden(overridden: boolean): void;
}
/**
* Maps one PropertySchema entry to a labelled row containing the matching widget.
* The inspector is entirely generated through this function: adding a new property
* kind means adding a case here, adding a property means adding a schema line.
*/
export function createFieldRow(def: PropertyDef, value: PropertyValue, handlers: FieldHandlers, onReset?: () => void): FieldRow {
const row = new HStack({ gap: Theme.gap, align: 'center' });
row.minHeight = Theme.rowHeight;
const label = new Label(def.label, { style: 'muted' });
label.minWidth = 64;
row.add(label);
let setValue: (v: PropertyValue) => void;
switch (def.kind) {
case 'number': {
const field = new NumberField(Number(value), {
min: def.min,
max: def.max,
step: def.step ?? 1,
onInput: handlers.onInput,
onChange: handlers.onChange,
});
row.add(field, 1);
setValue = (v) => field.setNumber(Number(v));
break;
}
case 'text': {
const field = new TextField(String(value), { onInput: handlers.onInput, onChange: handlers.onChange });
row.add(field, 1);
setValue = (v) => field.setValue(String(v));
break;
}
case 'color': {
const field = new ColorField(Number(value), { onInput: handlers.onInput, onChange: handlers.onChange });
row.add(field, 1);
setValue = (v) => field.setValue(Number(v));
break;
}
case 'boolean': {
const field = new Checkbox(Boolean(value));
field.onChange = handlers.onChange;
row.add(field);
row.add(new Label(''), 1);
setValue = (v) => field.setChecked(Boolean(v));
break;
}
}
let reset: Button | null = null;
if (onReset) {
reset = new Button('↺', { variant: 'ghost', width: Theme.controlHeight, onClick: onReset });
reset.visible = false;
row.add(reset);
}
return {
row,
setValue,
setOverridden(overridden) {
label.setStyle(overridden ? 'bodyBold' : 'muted');
if (reset && reset.visible !== overridden) {
reset.visible = overridden;
row.relayout();
}
},
};
}