The Container tab reuses SceneViewport against any object holder, so a container template is edited exactly like a scene. Right-click no longer changes the selection, which previously made nesting impossible: opening the menu on a container loaded it, and "Place in container" then targeted itself. Template edits (move, rename, override) now reach already-placed instances per slot, through a shared templateSync used by both the apply path and the plain object commands. Copies the user has touched keep their own value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
import { isHandleEnabled, worldPositionOf } from '../core/graph';
|
||
import { type AssetType, isSymbolType } from '../core/model/AssetTypes';
|
||
import { editorPosition, treeOrder } from '../core/model/hierarchy';
|
||
import type { PropertyBag } from '../core/model/Properties';
|
||
import { findAsset, type ObjectHolder, type Project } from '../core/model/Project';
|
||
import { resolveTexture, type TextureRef } from '../core/model/texture';
|
||
import { enabledAlongChain } from '../core/properties/enabled';
|
||
import { resolveSceneObjectProps } from '../core/properties/resolve';
|
||
import type { World } from '../runtime/World';
|
||
|
||
/** What the viewport draws: the same shape whether it comes from the model or the world. */
|
||
export interface DrawItem {
|
||
id: string;
|
||
type: AssetType;
|
||
name: string;
|
||
/** World position, parents already applied, in the scene view including editor offsets. */
|
||
x: number;
|
||
y: number;
|
||
z: number;
|
||
props: PropertyBag;
|
||
/** Id of the parent item, empty when top-level. */
|
||
parentId: string;
|
||
/** Effective switch: this object and all its parents are on (see `properties/enabled.ts`). */
|
||
enabled: boolean;
|
||
/** Resolved here because drawing has no access to the project. */
|
||
texture: TextureRef | null;
|
||
}
|
||
|
||
interface DrawList {
|
||
items: DrawItem[];
|
||
background: number;
|
||
}
|
||
|
||
/**
|
||
* A scene or a container template, in tree order – the tie-break for equal `z` – at the editor
|
||
* positions. A container has no background of its own, so it keeps the fallback.
|
||
*/
|
||
export function collectHolderItems(
|
||
project: Project,
|
||
holder: ObjectHolder | undefined,
|
||
fallbackBackground: number,
|
||
): DrawList {
|
||
if (!holder) return { items: [], background: fallbackBackground };
|
||
const items: DrawItem[] = [];
|
||
for (const { object } of treeOrder(holder)) {
|
||
const asset = findAsset(project, object.assetId);
|
||
if (!asset) continue;
|
||
const props = resolveSceneObjectProps(asset, object);
|
||
items.push({
|
||
id: object.id,
|
||
type: asset.type,
|
||
name: object.name || asset.name,
|
||
...editorPosition(holder, object),
|
||
props,
|
||
parentId: object.parentId,
|
||
enabled: true,
|
||
texture: asset.type === 'sprite' ? resolveTexture(project, props) : null,
|
||
});
|
||
}
|
||
const byId = new Map(items.map((i) => [i.id, i]));
|
||
for (const item of items)
|
||
item.enabled = enabledAlongChain(
|
||
item,
|
||
(i) => i.props,
|
||
(i) => byId.get(i.parentId) ?? null,
|
||
);
|
||
return { items, background: Number(holder.props.backgroundColor ?? fallbackBackground) };
|
||
}
|
||
|
||
/** The running world: scene content only, since symbols are editor-only. */
|
||
export function collectWorldItems(world: World): DrawList {
|
||
const items = world.entities
|
||
.filter((e) => !isSymbolType(e.type) && isHandleEnabled(e))
|
||
.map((e) => ({
|
||
id: e.objectId,
|
||
type: e.type,
|
||
name: e.name,
|
||
...worldPositionOf(e),
|
||
props: e.props,
|
||
parentId: e.parent?.objectId ?? '',
|
||
enabled: true,
|
||
// Resolved per frame on purpose: a behavior may swap `texture` with `Set property`.
|
||
texture: e.type === 'sprite' ? resolveTexture(world.project, e.props) : null,
|
||
}));
|
||
return { items, background: world.backgroundColor };
|
||
}
|