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:

reference