build method¶
Build() is the function you implement on a GooPanel<TRoot>. it returns the tree you want on screen.
goo diffs that tree, mutates the engine Panel tree to match, and waits for the next change.
when it runs¶
Build() runs on the next frame after _needsBuild flips to true. four triggers:
- first frame.
OnEnabledsets the flag on enable. - rebuild call. your
Rebuild()sets it. - hotload. the engine fires
IHotloadManaged.Createdafter Cecil migrates field values onto a fresh instance. - re-enable. disable then enable resets the fiber and sets the flag.
each frame, OnUpdate reads the flag. if set, it runs Build(), diffs the result against the prior tree, applies the ops to Panel.Children, then clears the flag.
rebuild()¶
the tree on screen is whatever Build() last returned. when your data changes, that tree is stale until Build() runs again. Rebuild() is how you ask for it.
mutate a field, call Rebuild():
public class CounterUI : GooPanel<Container>
{
int _count;
void Increment() { _count++; Rebuild(); }
protected override Container Build() => new Container
{
FlexDirection = FlexDirection.Column,
Children = { new Text($"count: {_count}") },
};
}
your data lives on the component. children are pure functions of that data.
hotload just works¶
edit your Build() and save. the engine creates a fresh instance, Cecil-migrates field values, fires IHotloadManaged.Created.
goo treats that as a rebuild trigger. the panel reflects your edits the next frame.
per-frame rebuilds¶
one approach for live updates or animation. override OnUpdate, call Rebuild() before base.OnUpdate():
protected override void OnUpdate()
{
Rebuild();
base.OnUpdate();
}
the radial wheel example reads Time.Now inside Build() to animate a spinning highlight this way.
for pure visual animation, engine-side transitions (TransitionMs) and CSS animations (the Animation* family on Container) run without rebuilding the tree.
structural diff¶
after Build() returns, the reconciler compares the new tree against the prior tree. it emits the minimum set of ops (create, remove, update style, reorder) to bring the engine Panel tree into line.
equality is per-blob:
Container.Keyplus style.Text.ContentplusKeyplus style.Image.TextureplusPathplusKeyplus style.
children are not part of a blob's equality. each child list gets its own recursive diff pass.
editing one character of one text reruns the whole Build(), but the diff only emits an op for that text. siblings are not effected.
keys¶
by default the reconciler matches children by position. if a list reorders between rebuilds (sort, filter), set Key so identity follows the move:
new Container
{
FlexDirection = FlexDirection.Column,
Children =
{
new Text("Alice") { Key = "u1" },
new Text("Bob") { Key = "u2" },
new Text("Carol") { Key = "u3" },
},
}
a keyed reorder emits a single MoveAt op instead of rewriting content in place. without keys, a reorder reads as content changes to the diff.
unkeyed reordering is fine for static lists but lossy for any item with focus or animation.
two warnings hint when you want keys:
- keyless child list changed length - add keys.
- mixed keyed and unkeyed children in one list. the reconciler falls back to positional matching and ignores keys.
three composition patterns¶
initializer - init-only properties on the record:
new Container
{
Padding = 16,
BackgroundColor = Color.White,
BorderRadius = 8,
}
wrap. children sit inside the same initializer. two equivalent forms:
// bare form. no other init properties on the container.
new Container
{
new Text("hello"),
new Text("world"),
}
// explicit form. needed whenever the container also has init properties.
new Container
{
FlexDirection = FlexDirection.Column,
Children =
{
new Text("hello"),
new Text("world"),
},
}
C# does not allow mixing init properties with bare child items in the same braces (CS0747).
if the container has properties, use Children = { ... }.
Children.Add(...) after construction still works for loop-built or conditional children:
var list = new Container { FlexDirection = FlexDirection.Column, Children = { Header() } };
foreach (var item in items) list.Children.Add(Row(item));
extract - a static helper that returns a styled blob (see styles for the full pattern):
static Container Card(string title) => new Container
{
Padding = 16,
BackgroundColor = Color.White,
Children = { new Text(title) { FontWeight = 600 } },
};
there is no chained-modifier syntax on purpose. anything more than two chains should be extracted.