events¶
every blob takes init-only event handlers — Action delegates that fire when the user clicks, hovers, or presses on the blob. five events, same surface on Container, Text, and Image.
the basics¶
new Container
{
Padding = 12,
BackgroundColor = Color.White,
OnClick = () => Log.Info("clicked"),
}
that's it. attach a handler at construction time and the engine routes the event to it.
the five events:
| event | fires when |
|---|---|
OnClick |
the user releases a click on the blob. |
OnMouseEnter |
the cursor enters the blob's box. |
OnMouseLeave |
the cursor leaves the blob's box. |
OnMouseDown |
the user presses a mouse button while over the blob. |
OnMouseUp |
the user releases a mouse button while over the blob. |
all five are Action (void, no arguments). all five are optional. each event fires on its own blob only; events do not bubble to the parent.
a button¶
helpers compose with events the same way they compose with style:
public class MyMenu : GooPanel<Container>
{
protected override Container Build() => new Container
{
FlexDirection = FlexDirection.Column,
Gap = 8,
Button("play", () => Log.Info("play")),
Button("quit", () => Log.Info("quit")),
};
static Container Button(string label, Action onClick) => new Container
{
Padding = 12,
BackgroundColor = Color.White,
BorderRadius = 6,
OnClick = onClick,
new Text(label),
};
}
the helper closes over onClick like any other parameter. each rebuild constructs fresh Container values with fresh Action delegates; goo's reconciler refreshes the engine handler slot without recreating the panel.
hover state¶
OnMouseEnter and OnMouseLeave fire in pairs. use them when you need to run logic on hover that doesn't fit a :hover style variant (for example, opening a tooltip, prefetching data, or starting a custom animation):
new Container
{
Padding = 8,
OnMouseEnter = () => _showTooltip = true,
OnMouseLeave = () => _showTooltip = false,
new Text(label),
}
if all you need is a different background or font color on hover, use the style variants (HoverBackgroundColor, HoverFontColor) instead. they don't need a rebuild to flip.
pointer events auto-gate¶
s&box's engine only delivers mouse events to panels whose PointerEvents is All. goo handles this for you: when any of the five handlers is set, goo automatically sets PointerEvents = PointerEvents.All on the host panel.
you do not need to set it yourself.
the explicit override¶
if you set PointerEvents directly in your style, your value wins. this matters when you want to disable interaction:
new Container
{
OnClick = () => Log.Info("clicked"),
PointerEvents = PointerEvents.None, // your value wins; the click will not reach.
}
the click handler is registered, but the engine filters out the event before it can fire. this is usually a footgun, not a feature. if you want to suppress clicks conditionally, gate the handler body itself:
new Container
{
OnClick = () => { if (!_disabled) DoTheThing(); },
}
handlers and structural equality¶
goo's reconciler treats events differently from style: handler refs change every rebuild (each lambda allocates a fresh delegate), but the panel does not get recreated. the reconciler emits a single op per build that refreshes the engine's handler slot, and structural equality (which would normally trigger a panel recreate) ignores the handler fields entirely.
practical implications:
- you can write
OnClick = () => { ... }inline inBuild()without worrying about per-frame allocation churn forcing reconciliation work. the handler ref changes, the panel does not. - holding a stable
Actioninstance in a field and reusing it is equivalent in behavior; the reconciler does not compare refs. - if you want to remove a handler, omit it on the next build. the engine's slot clears on the next reconciler pass.