Search documentation of Plushie

# Plushie.Widget.Handler behaviour (Plushie v0.7.2)

Copy Markdown View Source

Runtime support for stateful widgets.

Canvas widgets are pure-Elixir widgets that render via canvas shapes, manage internal state (hover, focus, animation), and transform raw canvas events into semantic widget events via `handle_event/2`.

## Widget lifecycle

Canvas widgets follow the standard Widget protocol pipeline:

1. `new/2` returns a struct (like all other widgets)
2. `Widget.to_node/1` produces a placeholder node tagged with the module and props as metadata
3. During `Tree.normalize`, the placeholder is detected and rendered with the best available state (stored from a previous cycle, or initial defaults for new widgets)
4. The rendered output is normalized in place. No post-processing

The tree carries widget state in `:meta` (via `%Meta.Composite{}`), making it the single source of truth. The runtime derives a registry from the tree after each render for O(1) event dispatch lookups.

## Event dispatch (captured/ignored model)

`invoke_handler/4` is called by the runtime when an event arrives for a widget inside a widget's scope. It calls the module's `handle_event/2` and interprets the return value using iced's captured/ignored model:

- `{:emit, family, data}` - captured, emit semantic event
- `{:emit, family, data, new_state}` - captured, emit + update state
- `{:update_state, new_state}` - captured, internal state change only
- `:consumed` - captured, suppress event
- `:ignored` - not captured, continue to next handler in scope chain

Events are dispatched through the scope chain (innermost to outermost). `:ignored` continues to the next widget handler in the chain. Captured events stop propagation (or, for `:emit`, replace the event and continue). If no handler captures, the event reaches `app.update/2`.

## Summary

### Types

handle_event_result()

Valid return values from `handle_event/2`.

### Callbacks

handle_event(event, state)

Transforms a raw event into a semantic widget event (or ignores it).

subscribe(props, state)

Returns subscription specs for this widget (optional).

view(id, props, state)

Returns the widget tree for this widget given its id, resolved props, and internal state.

### Functions

invoke_handler(module, event, state, widget_id \\ "", window_id \\ nil)

Invokes a widget's handle_event/2 and interprets the result.

widget_states_key()

Process dictionary key used to pass canvas widget states during normalization.

## Types

# handle_event_result()

    @type handle_event_result() ::
      {:emit, atom(), term()}
      | {:emit, atom(), term(), map()}
      | {:update_state, map()}
      | :consumed
      | :ignored

Valid return values from `handle_event/2`.

## Callbacks

# handle_event(event, state)

    @callback handle_event(event :: struct(), state :: map()) :: handle_event_result()

Transforms a raw event into a semantic widget event (or ignores it).

# subscribe(props, state)

(optional)

    @callback subscribe(props :: map(), state :: map()) :: [Plushie.Subscription.t()]

Returns subscription specs for this widget (optional).

# view(id, props, state)

    @callback view(id :: String.t(), props :: map(), state :: map()) :: map()

Returns the widget tree for this widget given its id, resolved props, and internal state.

## Functions

# invoke_handler(module, event, state, widget_id \\ "", window_id \\ nil)

    @spec invoke_handler(
      module :: module(),
      event :: struct(),
      state :: map(),
      widget_id :: String.t(),
      window_id :: String.t() | nil
    ) :: {{:emit, struct()} | :consumed | :ignored, map()}

Invokes a widget's handle_event/2 and interprets the result.

Returns `{action, new_state}` where action is one of:

- `{:emit, %WidgetEvent{}}` - captured with transformed event
- `:consumed` - captured, no output
- `:ignored` - not captured, continue to next handler

# widget_states_key()

    @spec widget_states_key() :: atom()

Process dictionary key used to pass canvas widget states during normalization.