setSceneProperty function
Write one authored property by the name a SceneNode.bindings entry keys it under. The one place that maps a property name to a slot, so an argument and a repeated item's field land the same way.
Implementation
void setSceneProperty(SceneNode node, String prop, Object? value) {
switch (resolveSceneKey(node, prop)) {
case ScenePropertyKey(:var prop):
prop.write(node, switch (prop.kind) {
// A number filling a text slot is ordinary in a repeated row — a
// quantity is a number and reads as one, not as "12.0".
ScenePropKind.string => switch (value) {
// A nullable string property — a font family — takes "nothing
// set".
null => null,
double d when d == d.roundToDouble() && d.abs() < 1e15 =>
'${d.round()}',
_ => '$value',
},
ScenePropKind.number => (value! as num).toDouble(),
ScenePropKind.edges => prop.quad!(value),
ScenePropKind.size => sizeFromWire(value),
_ => value,
});
case ScenePartKey(:var prop, :var part)
when prop.kind == ScenePropKind.args:
// Null is "not written": the argument leaves the map, so the file
// spells nothing and the widget's own fallback answers.
switch (node) {
case SceneRefNode r:
value == null ? r.args.remove(part) : r.args[part] = value;
case ExternalNode e:
value == null ? e.args.remove(part) : e.args[part] = value;
default:
break;
}
case ScenePartKey(:var prop, :var part)
when prop.kind == ScenePropKind.axes:
var axes = Map<String, double>.of(
prop.read(node)! as Map<String, double>,
);
// Null puts the axis back at whatever the face itself defaults to,
// which is what an absent tag means.
value == null
? axes.remove(part)
: axes[part] = (value as num).toDouble();
prop.write(node, axes);
case ScenePartKey(:var prop, :var sideIndex):
// One side moves, the other three stay: a side is a number on a value
// that is four of them.
if (value == null) return;
var current = prop.read(node)! as SceneQuad;
prop.write(node, current.withSide(sideIndex, (value as num).toDouble()));
case null:
break;
}
}