import type { AssetType } from '../model/AssetTypes'; import type { PropertyBag, PropertyValue } from '../model/Properties'; /** * Declarative description of one editable property. The inspector is generated from * these entries, so adding a property to a type is a single line in a schema below. */ export type PropertyDef = | { key: string; label: string; kind: 'number'; default: number; min?: number; max?: number; step?: number } | { key: string; label: string; kind: 'text'; default: string } | { key: string; label: string; kind: 'color'; default: number } | { key: string; label: string; kind: 'boolean'; default: boolean }; export type PropertyKind = PropertyDef['kind']; export type PropertySchema = readonly PropertyDef[]; /** Property schema per asset type. Position is deliberately absent: it belongs to scene objects. */ export const ASSET_SCHEMAS: Record = { scene: [{ key: 'backgroundColor', label: 'Background', kind: 'color', default: 0x2a2d34 }], camera: [ { key: 'width', label: 'View width', kind: 'number', default: 640, min: 16, step: 1 }, { key: 'height', label: 'View height', kind: 'number', default: 360, min: 16, step: 1 }, { key: 'zoom', label: 'Zoom', kind: 'number', default: 1, min: 0.1, max: 10, step: 0.1 }, ], sprite: [ { key: 'width', label: 'Width', kind: 'number', default: 100, min: 1, step: 1 }, { key: 'height', label: 'Height', kind: 'number', default: 100, min: 1, step: 1 }, { key: 'color', label: 'Color', kind: 'color', default: 0xe0553f }, ], }; /** Properties every scene object has regardless of its asset type. */ export const SCENE_OBJECT_SCHEMA: PropertySchema = [ { key: 'x', label: 'X', kind: 'number', default: 0, step: 1 }, { key: 'y', label: 'Y', kind: 'number', default: 0, step: 1 }, ]; export function schemaFor(type: AssetType): PropertySchema { return ASSET_SCHEMAS[type]; } export function defaultsFor(type: AssetType): PropertyBag { const bag: PropertyBag = {}; for (const def of ASSET_SCHEMAS[type]) bag[def.key] = def.default; return bag; } /** Clamp / coerce a raw value so it satisfies the definition. Unknown kinds are returned as-is. */ export function coerceValue(def: PropertyDef, value: PropertyValue): PropertyValue { switch (def.kind) { case 'number': { let n = typeof value === 'number' ? value : Number(value); if (!Number.isFinite(n)) n = def.default; if (def.min !== undefined) n = Math.max(def.min, n); if (def.max !== undefined) n = Math.min(def.max, n); return n; } case 'color': { const n = typeof value === 'number' ? value : parseInt(String(value).replace('#', ''), 16); return Number.isFinite(n) ? Math.max(0, Math.min(0xffffff, Math.floor(n))) : def.default; } case 'boolean': return Boolean(value); case 'text': return String(value); } }