feat: add node spawners (#149)

This commit is contained in:
Jacob
2023-01-21 20:19:27 +01:00
committed by GitHub
parent 3abee4f89e
commit da18a4e241
5 changed files with 74 additions and 14 deletions

View File

@@ -495,18 +495,19 @@ export class Layout extends Node {
@computed()
protected layoutChildren(): Layout[] {
this.element.innerText = '';
const queue = [...this.children()];
const result: Layout[] = [];
const elements: HTMLElement[] = [];
while (queue.length) {
const child = queue.shift();
if (child instanceof Layout) {
this.element.append(child.element);
result.push(child);
elements.push(child.element);
} else if (child) {
queue.push(...child.children());
}
}
this.element.replaceChildren(...elements);
return result;
}

View File

@@ -5,6 +5,7 @@ import {
getPropertiesOf,
initial,
initialize,
inspectable,
signal,
vector2Signal,
wrapper,
@@ -19,7 +20,7 @@ import {
Vector2Signal,
ColorSignal,
} from '@motion-canvas/core/lib/types';
import {ReferenceReceiver} from '@motion-canvas/core/lib/utils';
import {DetailedError, ReferenceReceiver} from '@motion-canvas/core/lib/utils';
import type {ComponentChild, ComponentChildren, NodeConstructor} from './types';
import {Promisable} from '@motion-canvas/core/lib/threading';
import {useScene2D} from '../scenes/useScene2D';
@@ -36,11 +37,13 @@ import {
SignalValue,
SimpleSignal,
isReactive,
Computed,
} from '@motion-canvas/core/lib/signals';
export interface NodeProps {
ref?: ReferenceReceiver<any>;
children?: ComponentChildren;
spawner?: SignalValue<Node[]>;
key?: string;
x?: SignalValue<number>;
@@ -228,13 +231,58 @@ export class Node implements Promisable<Node> {
return filters;
}
public readonly children = createSignal<Node[]>([]);
@inspectable(false)
@cloneable(false)
@initial([])
@signal()
protected declare readonly spawner: SimpleSignal<Node[], this>;
@inspectable(false)
@cloneable(false)
@signal()
public declare readonly children: SimpleSignal<Node[], this>;
protected setChildren(value: SignalValue<Node[]>) {
this.spawner(value);
}
protected getChildren(): Node[] {
this.spawnChildren();
return this.realChildren;
}
@computed()
protected spawnChildren() {
const children = this.spawner();
if (isReactive(this.spawner.context.raw())) {
const keep = new Set<string>();
for (const realChild of children) {
const current = realChild.parent.context.raw();
if (current && current !== this) {
throw new DetailedError(
'The spawner returned a node that already has a parent',
'A spawner should either create entirely new nodes or reuse nodes from a pool.',
);
}
realChild.parent(this);
keep.add(realChild.key);
}
for (const realChild of this.realChildren) {
if (!keep.has(realChild.key)) {
realChild.parent(null);
}
}
this.realChildren = children;
} else {
this.realChildren = children;
}
}
private realChildren: Node[] = [];
public readonly parent = createSignal<Node | null>(null);
public readonly properties = getPropertiesOf(this);
public readonly key: string;
public readonly creationStack?: string;
public constructor({children, key, ...rest}: NodeProps) {
public constructor({children, spawner, key, ...rest}: NodeProps) {
this.key = useScene2D()?.registerNode(this, key) ?? key ?? '';
this.creationStack = new Error().stack;
initialize(this, {defaults: rest});
@@ -242,6 +290,9 @@ export class Node implements Promisable<Node> {
signal.reset();
}
this.add(children);
if (spawner) {
this.children(spawner);
}
}
@computed()
@@ -342,6 +393,10 @@ export class Node implements Promisable<Node> {
public insert(node: ComponentChildren, index = 0): this {
const array: ComponentChild[] = Array.isArray(node) ? node : [node];
if (array.length === 0) {
return this;
}
const children = this.children();
const newChildren = children.slice(0, index);
@@ -452,7 +507,9 @@ export class Node implements Promisable<Node> {
*/
public clone(customProps: NodeProps = {}): this {
const props: NodeProps & Record<string, any> = {...customProps};
if (this.children().length > 0) {
if (isReactive(this.spawner.context.raw())) {
props.spawner = this.spawner.context.raw();
} else if (this.children().length > 0) {
props.children ??= this.children().map(child => child.clone());
}
@@ -460,6 +517,7 @@ export class Node implements Promisable<Node> {
if (!meta.cloneable || key in props) continue;
if (meta.compound) {
for (const [key, property] of meta.compoundEntries) {
if (property in props) continue;
props[property] = (<Record<string, SimpleSignal<any>>>(
(<unknown>signal)
))[key].context.raw();

View File

@@ -60,7 +60,10 @@ export class Scene2D extends GeneratorScene<View2D> implements Inspectable {
}
public inspectPosition(x: number, y: number): InspectedElement | null {
return this.view.hit(new Vector2(x, y))?.key ?? null;
startScene(this);
const node = this.view.hit(new Vector2(x, y))?.key ?? null;
endScene(this);
return node;
}
public validateInspection(

View File

@@ -25,7 +25,7 @@ export class DependencyContext<TOwner = void> {
const handle: PromiseHandle<T | null> = {
promise,
value: initialValue,
stack: this.collectionStack[0]?.stack,
stack: new Error().stack,
};
const context = this.collectionStack.at(-2);
@@ -51,11 +51,14 @@ export class DependencyContext<TOwner = void> {
protected dependencies = new Set<Subscribable<void>>();
protected event = new FlagDispatcher();
protected stack: string | undefined;
protected markDirty = () => this.event.raise();
public constructor(protected readonly owner: TOwner) {
this.invokable = this.invoke.bind(this);
Object.defineProperty(this.invokable, 'context', {
value: this,
});
}
protected invoke() {
@@ -71,13 +74,11 @@ export class DependencyContext<TOwner = void> {
);
}
this.stack = new Error().stack;
DependencyContext.collectionSet.add(this);
DependencyContext.collectionStack.push(this);
}
protected finishCollecting() {
this.stack = undefined;
DependencyContext.collectionSet.delete(this);
if (DependencyContext.collectionStack.pop() !== this) {
throw new Error('collectStart/collectEnd was called out of order.');

View File

@@ -66,9 +66,6 @@ export class SignalContext<
Object.defineProperty(this.invokable, 'save', {
value: this.save.bind(this),
});
Object.defineProperty(this.invokable, 'context', {
value: this,
});
if (this.initial !== undefined) {
this.current = this.initial;