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:
1 parent
aaa8154adf
commit
d4b1ffa051
18 files changed
+232
-63
No files matched your search
+1
-1
@@ -50,7 +50,7 @@ function installDebugHook(app: Application, state: EditorState, viewport: Viewpo
|
||||
const p = node.getGlobalPosition();
|
||||
return { x: p.x + node.width / 2, y: p.y + node.height / 2 };
|
||||
};
|
||||
(window as unknown as { __gm: unknown }).__gm = { state, locate, viewportCenter, viewportInfo: () => viewport.debugInfo(), layerOf: (id: string) => viewport.layerOf(id) };
|
||||
(window as unknown as { __gm: unknown }).__gm = { state, locate, viewportCenter, viewportInfo: () => viewport.debugInfo(), layerOf: (id: string) => viewport.layerOf(id), drawOrder: () => viewport.drawOrder() };
|
||||
}
|
||||
|
||||
class Editor {
|
||||
|
||||
@@ -13,8 +13,11 @@ 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;
|
||||
/**
|
||||
* Highlight the row as deviating from its base value and show the reset button:
|
||||
* an override on scene objects, a non-default value on assets.
|
||||
*/
|
||||
setModified(modified: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,10 +79,10 @@ export function createFieldRow(def: PropertyDef, value: PropertyValue, handlers:
|
||||
return {
|
||||
row,
|
||||
setValue,
|
||||
setOverridden(overridden) {
|
||||
label.setStyle(overridden ? 'bodyBold' : 'muted');
|
||||
if (reset && reset.visible !== overridden) {
|
||||
reset.visible = overridden;
|
||||
setModified(modified) {
|
||||
label.setStyle(modified ? 'bodyBold' : 'muted');
|
||||
if (reset && reset.visible !== modified) {
|
||||
reset.visible = modified;
|
||||
row.relayout();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user