Plushie.Widget.Handler behaviour (Plushie v0.6.0)

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 (__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

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.