Add the Variable node and show the variable name in node titles

Get/Set variable nodes now title themselves "Get <name>" / "Set <name>", and
the new Variable node just "<name>"; the title is the same folded or open,
and a collapsed node shrinks to it. The redundant "Variable: <name>" row is
gone unless the variable was deleted ("<name>?" is the only hint then).

The Variable node sets its value when an exec flow arrives and exposes the
value at any time. Its output is read when a downstream node pulls it, not
frozen at the write, so count = count + 1 is one Variable node plus an Add.
Get variable keeps reading once per pulse: switching it would change the
behaviour of existing games.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-11 13:34:09 +02:00
1 parent 5f8a308d62
commit e9fa592078
19 files changed
+354 -38

No files matched your search

+21 -8
View File
@@ -1,4 +1,4 @@
import { Graphics, Rectangle, Text } from 'pixi.js';
import { CanvasTextMetrics, Graphics, Rectangle, Text } from 'pixi.js';
import {
asString,
edgesInto,
@@ -8,6 +8,7 @@ import {
inlineValue,
type NodeCategory,
type NodeDef,
nodeDef,
type PortDef,
type PortType,
type Value,
@@ -19,6 +20,7 @@ import { assetRefLabel } from '../../core/properties/assetRef';
import type { PropertyDef } from '../../core/properties/PropertySchema';
import { formatNumber, Theme, textStyle, type TextStyleToken, truncate, Widget } from '../../ui';
import { keyLabel } from '../inspector/fieldFactory';
import { collapsedNodeWidth } from './folding';
/** Node geometry in graph units; exported because edge anchors must land exactly on these ports. */
export const NODE_WIDTH = 176;
@@ -30,6 +32,9 @@ export const PORT_HIT_RADIUS = 9;
const PAD = 6;
/** Room in front of the title for the collapse chevron, as wide as the hierarchy's. */
const CHEVRON_SLOT = 14;
const TITLE_X = PAD + CHEVRON_SLOT;
/** Right of a collapsed title: the side's link dot sits centred on the edge. */
const COLLAPSED_TITLE_END = PAD + PORT_RADIUS;
export function categoryColor(category: NodeCategory): number {
const c = Theme.colors;
@@ -126,15 +131,17 @@ export class NodeView extends Widget {
this.def = def;
this.problem = problem;
const hidden = new Set(this.def.hiddenParams?.(node.params) ?? []);
const title = this.def.label;
const derived = title !== nodeDef(node.type)?.label;
this.paramLines = this.def.params
.filter((p) => !hidden.has(p.key))
.map((p) => ({
label: p.label,
value: formatParam(p, node.params[p.key] ?? p.default, asset, project),
}))
// A setting the title already states is not worth a row: a nested-behavior node is titled
// with the behavior it runs, so "Behavior: Double" under "Double" says nothing new.
.filter((p) => p.value !== this.def.label)
// A title made from a setting states it ("Behavior: Double" under "Double", "Variable: speed"
// under "Get speed"). A dangling reference keeps its row: "speed?" is not in the title.
.filter((p) => !derived || (p.value !== title && !title.endsWith(` ${p.value}`)))
.map((p) => `${p.label}: ${p.value}`);
this.connected = new Set(
inlineInputs(this.def)
@@ -146,17 +153,23 @@ export class NodeView extends Widget {
outputs: graph.edges.some((e) => e.from.node === node.id),
};
const height = this.collapsed ? TITLE_HEIGHT : this.headerHeight() + this.rowCount() * ROW_HEIGHT + PAD;
// Sized before the texts: output labels are placed against the right edge.
this.resize(this.collapsed ? this.collapsedWidth() : NODE_WIDTH, height);
this.rebuildTexts();
this.resize(NODE_WIDTH, height);
this.position.set(node.x, node.y);
}
private collapsedWidth(): number {
const title = CanvasTextMetrics.measureText(this.def.label, textStyle('accent')).width;
return collapsedNodeWidth(title, TITLE_X + COLLAPSED_TITLE_END, NODE_WIDTH);
}
get collapsed(): boolean {
return this.node.collapsed === true;
}
chevronAt(localX: number, localY: number): boolean {
return localY >= 0 && localY < TITLE_HEIGHT && localX >= PAD - 2 && localX < PAD + CHEVRON_SLOT;
return localY >= 0 && localY < TITLE_HEIGHT && localX >= PAD - 2 && localX < TITLE_X;
}
chevronGlobal(): { x: number; y: number } {
@@ -233,10 +246,10 @@ export class NodeView extends Widget {
add(
this.def.label,
'accent',
PAD + CHEVRON_SLOT,
TITLE_X,
(TITLE_HEIGHT - 16) / 2,
'left',
NODE_WIDTH - PAD * 2 - CHEVRON_SLOT,
this.w - TITLE_X - (this.collapsed ? COLLAPSED_TITLE_END : PAD),
);
if (this.collapsed) return;
let y = TITLE_HEIGHT + PAD;