Z-order via position.z and reset-to-default for asset properties

- Position is now { x, y, z }; higher z draws on top, ties keep hierarchy order
- Viewport sorts the objects layer deterministically (syncDrawOrder)
- Inspector shows a Z field; asset properties that differ from the schema
  default are marked bold with a reset button (ResetAssetPropertyCommand)
- isModified() helper, unit tests, E2E checks and seed updated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 11:42:18 +02:00
1 parent aaa8154adf
commit d4b1ffa051
18 files changed
+232 -63

No files matched your search

+35 -24
View File
@@ -1,14 +1,14 @@
import { RenameAssetCommand, SetAssetPropertyCommand } from '../../core/commands/assetCommands';
import { RenameAssetCommand, ResetAssetPropertyCommand, SetAssetPropertyCommand } from '../../core/commands/assetCommands';
import {
ClearOverrideCommand,
MoveSceneObjectCommand,
RenameSceneObjectCommand,
SetOverrideCommand,
} from '../../core/commands/sceneObjectCommands';
import type { PropertyValue } from '../../core/model/Properties';
import type { Position, PropertyValue } from '../../core/model/Properties';
import { findAsset, findScene, findSceneObject } from '../../core/model/Project';
import { schemaFor } from '../../core/properties/PropertySchema';
import { isOverridden, resolveAssetProps, resolveSceneObjectProps } from '../../core/properties/resolve';
import { isModified, isOverridden, resolveAssetProps, resolveSceneObjectProps } from '../../core/properties/resolve';
import { Button, HStack, Label, Panel, ScrollContainer, TextField, Theme, VStack } from '../../ui';
import { createFieldRow, type FieldRow } from '../inspector/fieldFactory';
import type { EditorState, Selection } from '../state';
@@ -66,25 +66,32 @@ export class Inspector extends Panel {
});
this.body.add(this.labelled('Name', this.nameField));
// Asset properties: values that differ from the schema default are marked and resettable.
const values = resolveAssetProps(asset);
for (const def of schemaFor(asset.type)) {
let original: PropertyValue | undefined;
const row = createFieldRow(def, values[def.key] ?? def.default, {
onInput: (value) => {
const target = findAsset(this.state.project, assetId);
if (!target) return;
if (original === undefined) original = target.props[def.key];
target.props[def.key] = value;
this.state.preview();
const row = createFieldRow(
def,
values[def.key] ?? def.default,
{
onInput: (value) => {
const target = findAsset(this.state.project, assetId);
if (!target) return;
if (original === undefined) original = target.props[def.key];
target.props[def.key] = value;
this.state.preview();
},
onChange: (value) => {
const target = findAsset(this.state.project, assetId);
if (!target) return;
if (original !== undefined) target.props[def.key] = original;
original = undefined;
this.state.execute(new SetAssetPropertyCommand(assetId, def.key, value));
},
},
onChange: (value) => {
const target = findAsset(this.state.project, assetId);
if (!target) return;
if (original !== undefined) target.props[def.key] = original;
original = undefined;
this.state.execute(new SetAssetPropertyCommand(assetId, def.key, value));
},
});
() => this.state.execute(new ResetAssetPropertyCommand(assetId, def.key)),
);
row.setModified(isModified(asset, def.key));
this.fields.set(def.key, row);
this.body.add(row.row);
}
@@ -110,10 +117,10 @@ export class Inspector extends Panel {
assetRow.add(new Button(asset.name, { variant: 'ghost', onClick: () => this.state.select({ kind: 'asset', assetId: asset.id }) }), 1);
this.body.add(assetRow);
// Position – belongs to the scene object only.
// Position – belongs to the scene object only. Z is the draw order (higher = on top).
this.body.add(new Label('POSITION', { style: 'title' }));
for (const axis of ['x', 'y'] as const) {
let original: { x: number; y: number } | undefined;
for (const axis of ['x', 'y', 'z'] as const) {
let original: Position | undefined;
const row = createFieldRow({ key: axis, label: axis.toUpperCase(), kind: 'number', default: 0, step: 1 }, object.position[axis], {
onInput: (value) => {
const target = this.currentObject(sceneId, objectId);
@@ -169,7 +176,7 @@ export class Inspector extends Panel {
},
() => this.state.execute(new ClearOverrideCommand(sceneId, objectId, def.key)),
);
row.setOverridden(isOverridden(object, def.key));
row.setModified(isOverridden(object, def.key));
this.fields.set(def.key, row);
this.body.add(row.row);
}
@@ -188,7 +195,10 @@ export class Inspector extends Panel {
if (!asset) return this.rebuild();
this.nameField?.setValue(asset.name);
const values = resolveAssetProps(asset);
for (const [key, row] of this.fields) row.setValue(values[key] ?? 0);
for (const [key, row] of this.fields) {
row.setValue(values[key] ?? 0);
row.setModified(isModified(asset, key));
}
return;
}
const object = this.currentObject(s.sceneId, s.objectId);
@@ -197,11 +207,12 @@ export class Inspector extends Panel {
this.nameField?.setValue(object.name);
this.fields.get('x')?.setValue(object.position.x);
this.fields.get('y')?.setValue(object.position.y);
this.fields.get('z')?.setValue(object.position.z);
const values = resolveSceneObjectProps(asset, object);
for (const def of schemaFor(asset.type)) {
const row = this.fields.get(def.key);
row?.setValue(values[def.key] ?? def.default);
row?.setOverridden(isOverridden(object, def.key));
row?.setModified(isOverridden(object, def.key));
}
}