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,7 +1,7 @@
|
||||
import { Container, type FederatedPointerEvent, type FederatedWheelEvent, Graphics, Text } from 'pixi.js';
|
||||
import { MoveSceneObjectCommand } from '../core/commands/sceneObjectCommands';
|
||||
import type { AssetType } from '../core/model/AssetTypes';
|
||||
import type { PropertyBag, Vec2 } from '../core/model/Properties';
|
||||
import { type Position, type PropertyBag, sortByZ, type Vec2 } from '../core/model/Properties';
|
||||
import { findAsset } from '../core/model/Project';
|
||||
import { cameraFrame, rectAround, rectsIntersect, type WorldRect } from '../core/properties/camera';
|
||||
import { resolveSceneObjectProps } from '../core/properties/resolve';
|
||||
@@ -16,6 +16,8 @@ interface DrawItem {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
/** Draw order (see `Position.z`). */
|
||||
z: number;
|
||||
props: PropertyBag;
|
||||
}
|
||||
|
||||
@@ -48,7 +50,7 @@ export class Viewport extends Widget {
|
||||
/** Everything that may extend past the viewport (world and gizmos) is clipped through `content`. */
|
||||
private readonly content = new Container();
|
||||
private readonly world = new Container();
|
||||
/** Scene objects (sprites) in hierarchy order. */
|
||||
/** Scene objects (sprites), ordered by `z` then hierarchy order (see `syncDrawOrder`). */
|
||||
private readonly objects = new Container();
|
||||
/**
|
||||
* Editor symbols (camera icon, labels). Always drawn above `objects` so that a sprite can never
|
||||
@@ -63,7 +65,7 @@ export class Viewport extends Widget {
|
||||
/** Edit-mode view: world point at the viewport centre and zoom. */
|
||||
private view = { x: 0, y: 0, zoom: 1 };
|
||||
private panning: { startX: number; startY: number; viewX: number; viewY: number } | null = null;
|
||||
private dragging: { objectId: string; sceneId: string; start: Vec2; pointer: Vec2; moved: boolean } | null = null;
|
||||
private dragging: { objectId: string; sceneId: string; start: Position; pointer: Vec2; moved: boolean } | null = null;
|
||||
private visibleCount = 0;
|
||||
|
||||
constructor(private readonly state: EditorState) {
|
||||
@@ -95,7 +97,7 @@ export class Viewport extends Widget {
|
||||
this.dragging.moved = true;
|
||||
const object = this.state.activeScene?.objects.find((o) => o.id === this.dragging?.objectId);
|
||||
if (!object) return;
|
||||
object.position = { x: Math.round(this.dragging.start.x + dx), y: Math.round(this.dragging.start.y + dy) };
|
||||
object.position = { x: Math.round(this.dragging.start.x + dx), y: Math.round(this.dragging.start.y + dy), z: this.dragging.start.z };
|
||||
this.state.preview();
|
||||
}
|
||||
});
|
||||
@@ -154,6 +156,11 @@ export class Viewport extends Widget {
|
||||
return node.parent === this.overlay ? 'overlay' : 'objects';
|
||||
}
|
||||
|
||||
/** Object ids in the order they are drawn, back to front (test hook). */
|
||||
drawOrder(): string[] {
|
||||
return this.objects.children.filter((node) => node.visible).map((node) => node.label);
|
||||
}
|
||||
|
||||
override measure(): Size {
|
||||
return { w: Math.max(this.minWidth, 200), h: Math.max(this.minHeight, 200) };
|
||||
}
|
||||
@@ -199,7 +206,7 @@ export class Viewport extends Widget {
|
||||
if (this.playWorld) {
|
||||
return {
|
||||
background: this.playWorld.backgroundColor,
|
||||
items: this.playWorld.entities.filter((e) => e.type !== 'camera').map((e) => ({ id: e.objectId, type: e.type, name: e.name, x: e.x, y: e.y, props: e.props })),
|
||||
items: this.playWorld.entities.filter((e) => e.type !== 'camera').map((e) => ({ id: e.objectId, type: e.type, name: e.name, x: e.x, y: e.y, z: e.z, props: e.props })),
|
||||
};
|
||||
}
|
||||
const scene = this.state.activeScene;
|
||||
@@ -208,7 +215,7 @@ export class Viewport extends Widget {
|
||||
for (const object of scene.objects) {
|
||||
const asset = findAsset(this.state.project, object.assetId);
|
||||
if (!asset) continue;
|
||||
items.push({ id: object.id, type: asset.type, name: object.name || asset.name, x: object.position.x, y: object.position.y, props: resolveSceneObjectProps(asset, object) });
|
||||
items.push({ id: object.id, type: asset.type, name: object.name || asset.name, x: object.position.x, y: object.position.y, z: object.position.z, props: resolveSceneObjectProps(asset, object) });
|
||||
}
|
||||
return { items, background: Number(scene.props.backgroundColor ?? Theme.colors.viewport) };
|
||||
}
|
||||
@@ -250,6 +257,7 @@ export class Viewport extends Widget {
|
||||
let node = this.nodes.get(item.id);
|
||||
if (!node) {
|
||||
node = new Graphics();
|
||||
node.label = item.id;
|
||||
node.cursor = 'move';
|
||||
node.on('pointerdown', (e: FederatedPointerEvent) => this.onItemPointerDown(item.id, e));
|
||||
this.nodes.set(item.id, node);
|
||||
@@ -278,10 +286,25 @@ export class Viewport extends Widget {
|
||||
this.labels.delete(id);
|
||||
}
|
||||
}
|
||||
this.syncDrawOrder(items);
|
||||
|
||||
if (editing) this.drawGizmos(items, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-attach the object nodes so that Pixi draws them by ascending `z`, ties in hierarchy
|
||||
* order. Done explicitly (instead of Pixi's lazy `sortableChildren`) so the order is
|
||||
* deterministic and hierarchy changes are honoured too.
|
||||
*/
|
||||
private syncDrawOrder(items: DrawItem[]): void {
|
||||
const ordered = sortByZ(items.filter((item) => !this.isSymbol(item)));
|
||||
this.objects.removeChildren();
|
||||
for (const item of ordered) {
|
||||
const node = this.nodes.get(item.id);
|
||||
if (node) this.objects.addChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
/** Items drawn as editor symbols rather than as scene content. */
|
||||
private isSymbol(item: DrawItem): boolean {
|
||||
return item.type === 'camera';
|
||||
|
||||
Reference in new issue
Block a user