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:
new/2returns a struct (like all other widgets)Widget.to_node/1produces a placeholder node tagged with the module and props as metadata- 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) - The rendered output is normalized in place -- no post-processing
The tree carries widget state in :meta (__widget_state__),
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
Valid return values from handle_event/2.
Callbacks
Transforms a raw event into a semantic widget event (or ignores it).
Returns subscription specs for this widget (optional).
Returns the widget tree for this widget given its id, resolved props, and internal state.
Functions
Invokes a widget's handle_event/2 and interprets the result.
Process dictionary key used to pass canvas widget states during normalization.
Types
Callbacks
@callback handle_event(event :: struct(), state :: map()) :: handle_event_result()
Transforms a raw event into a semantic widget event (or ignores it).
@callback subscribe(props :: map(), state :: map()) :: [Plushie.Subscription.t()]
Returns subscription specs for this widget (optional).
Returns the widget tree for this widget given its id, resolved props, and internal state.
Functions
@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
@spec widget_states_key() :: atom()
Process dictionary key used to pass canvas widget states during normalization.