# `Drafter.Widget`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/widget.ex#L1)

Behaviour for a Drafter widget: a component that owns state, draws itself into a
rect, and handles events.

A widget module does `use Drafter.Widget, opts` and implements at least `c:mount/1`
and `c:render/2`. Everything else is optional and has a default.

    defmodule MyWidgets.Counter do
      use Drafter.Widget, handles: [:keyboard, :press], focusable: true

      alias Drafter.Draw.{Segment, Strip}

      defstruct count: 0, focused: false

      def component_tag, do: :counter
      def from_component_opts(_args, opts), do: %{count: Keyword.get(opts, :count, 0)}

      @impl true
      def mount(props), do: %__MODULE__{count: Map.get(props, :count, 0)}

      @impl true
      def render(%__MODULE__{count: count}, _rect) do
        [Strip.new([Segment.new("count: #{count}", %{})])]
      end

      def handle_key(:up, state), do: {:ok, %{state | count: state.count + 1}}
      def handle_key(_key, state), do: {:bubble, state}

      def handle_press(_x, _y, state), do: {:ok, %{state | count: state.count + 1}}
    end

`component_tag/0` and `from_component_opts/2` are what let the widget appear in a
component tree as `{:counter, count: 3}`; they belong to
the widget registry, not to this behaviour. Without them a widget is still
usable as `{MyWidgets.Counter, %{count: 3}}`.

## `use Drafter.Widget` options

Handles mode — the default, chosen whenever `:traits` is absent or empty:

  * `:handles` - the event kinds the widget wants, as a list of atoms. Default `[]`.
    `:keyboard`, `:char`, `:paste`, `:press` (`:click` is accepted for it),
    `:mouse_up`, `:drag`, `:hover`, `:scroll`. An event kind that is not declared
    is never routed to the widget, whatever callbacks it exports.
  * `:focusable` - `boolean()`. Default `:keyboard in handles`. A focusable widget
    takes part in tab order and gets `:focused` set on its state by `{:focus}` and
    `{:blur}`.
  * `:capture_handles` - event kinds the widget sees during the capture phase,
    before they reach their target. Default `[]`; requires
    `c:handle_event_capture/2`.
  * `:scroll` - keyword list configuring default scrolling: `:direction`
    (`:horizontal` default), `:step` (`5` default), `:wrap` (`false` default).
    Default `nil`, which becomes `%{direction: :horizontal, step: 5}` when
    `:scroll` is in `:handles`.
  * `:layout_impact` - how far a state change invalidates layout: `:self`
    (default), `:below`, `:above`, `:left`, `:right`, `:parent`, or `:all`.

Trait mode — chosen when `:traits` is a non-empty list:

  * `:traits` - trait specs resolved by `Drafter.Widget.Trait`, e.g.
    `[:focusable, :scrollable]`. The traits supply the handles, focusability,
    default state, and the `handle_event/2` pipeline.
  * `:handles` - extra event kinds merged with the ones the traits declare.
  * `:scroll` - passed to the traits as `Drafter.Widget.Trait.scroll_config/2`
    reads it.
  * `:layout_impact` - as above.

Trait mode reads no `:focusable` and no `:capture_handles`: focusability comes
from the traits and the generated `__widget_capabilities__/0` reports
`capture_handles: []`.

Both modes generate `handle_event/2`, `__widget_capabilities__/0`, and
`__layout_impact__/0`, and overridable defaults for `c:mount/1`, `c:render/2`,
`c:update/2`, `c:unmount/1`, `focused/1`, `update_props_from_mount/3`, and
`preferred_height/2`. Trait mode additionally generates `__widget_traits__/0`,
`__widget_capabilities_bitmap__/0`, `__render_affecting_fields__/0`,
`__layout_static__/0`, and `__trait_default_state__/0`.

## Event callbacks and their results

`Drafter.Widget.EventRouter` turns each event into the matching callback, and only
when the kind is declared and the function is exported. Every event callback
returns one of:

  * `{:ok, new_state}` - handled. The event stops here and does not reach the
    parent.
  * `{:ok, new_state, actions}` - handled, plus a list of actions for the app; see
    `Drafter.EventResult`.
  * `{:bubble, new_state}` / `{:bubble, new_state, actions}` - the state is kept
    and the event continues to the parent widget, and then to the app.
  * `{:noreply, state}` - treated as *not handled*: the event continues **and the
    returned state is discarded**. Return `{:bubble, new_state}` to keep a change
    while still letting the event through.
  * an action tuple — `{:pop, result}`, `{:push, module, props}`,
    `{:replace, module, props}`, `{:app_callback, name, data}` - the action is
    recorded, the event stops, and the widget's state is left as it was before the
    event.

Anything else counts as not handled and the state is left unchanged.

## Paste

A widget receives pasted text only if it declares `:paste`:

    use Drafter.Widget, handles: [:keyboard, :paste], focusable: true

    def handle_paste(text, state), do: {:ok, %{state | value: state.value <> text}}

`text` has already been through `Drafter.Clipboard.sanitize/1` and carries no
escape sequences. A widget that does not declare `:paste` bubbles the event
instead. `Drafter.Clipboard.copy/1` is the other direction.

# `action`

```elixir
@type action() ::
  {:pop, term()}
  | {:push, module(), props()}
  | {:replace, module(), props()}
  | {:app_callback, atom(), term()}
```

An action a widget hands back to the app or the screen stack.

`Drafter.EventResult.parse/2` recognises exactly these four shapes, records them
and stops the event. `{:app_callback, name, data}` is the one the app itself
handles, through `handle_event/3`.

# `event_result`

```elixir
@type event_result() ::
  {:ok, state()}
  | {:ok, state(), [action()]}
  | {:bubble, state()}
  | {:bubble, state(), [action()]}
  | {:noreply, state()}
  | {:error, term()}
  | action()
```

What an event callback may return.

`Drafter.EventResult.parse/2` maps these onto `{state, actions, mode}`; anything
else, including `{:error, reason}`, counts as not handled and leaves the state
as it was.

# `expand_option`

```elixir
@type expand_option() :: :fill | :content | pos_integer()
```

# `key`

```elixir
@type key() :: atom()
```

# `layout_impact`

```elixir
@type layout_impact() :: :self | :below | :above | :left | :right | :all | :parent
```

# `modifiers`

```elixir
@type modifiers() :: [:ctrl | :alt | :shift | :meta]
```

# `props`

```elixir
@type props() :: map()
```

# `rect`

```elixir
@type rect() :: %{
  x: non_neg_integer(),
  y: non_neg_integer(),
  width: pos_integer(),
  height: pos_integer()
}
```

# `render_result`

```elixir
@type render_result() :: [Drafter.Draw.Strip.t()] | {:error, term()}
```

# `scroll_direction`

```elixir
@type scroll_direction() :: :up | :down
```

# `state`

```elixir
@type state() :: term()
```

# `apply_data_buffer`
*optional* 

```elixir
@callback apply_data_buffer(state(), Drafter.RingBuffer.t(), rect()) :: state()
```

Folds the items accumulated in the widget's data channel into its state.

Called when the throttle window of a widget declared with `:buffer` and `:refresh`
opens. `buffer` is the `Drafter.RingBuffer` holding everything pushed since the
last call; `rect` is the widget's current rect, so a widget can keep only as many
points as it can draw. Returns the new state.

# `handle_custom_event`
*optional* 

```elixir
@callback handle_custom_event(Drafter.Event.t(), state()) :: event_result()
```

Handles any event with no callback of its own — `{:timer, id}`, `{:custom, term}`,
and `{:char, codepoint}` when `:char` is not declared. Needs no entry in `:handles`.

# `handle_drag`
*optional* 

```elixir
@callback handle_drag(x :: integer(), y :: integer(), state()) :: event_result()
```

Handles mouse motion with a button held. Requires `:drag` in `:handles`.
`x` and `y` are absolute zero-based screen cells.

# `handle_event`

```elixir
@callback handle_event(Drafter.Event.t(), state()) :: event_result()
```

Handles an event.

Generated by `use Drafter.Widget` and rarely written by hand: the generated version
dispatches to `c:handle_key/2`, `c:handle_press/3`, and the rest according to the
declared handles. Overriding it replaces that dispatch entirely.

# `handle_event_capture`
*optional* 

```elixir
@callback handle_event_capture(Drafter.Event.Object.t(), state()) ::
  {:continue, Drafter.Event.Object.t(), state()}
  | {:stop, Drafter.Event.Object.t(), state(), list()}
  | {:prevent, Drafter.Event.Object.t(), state()}
```

Inspects an event on its way down to its target, before the target sees it.
Requires the event's kind in `:capture_handles`.

Returns `{:continue, event, state}` to let it carry on to the target,
`{:prevent, event, state}` to suppress the target's default behaviour, or
`{:stop, event, state, actions}` to end the dispatch here and emit `actions`.

# `handle_hover`
*optional* 

```elixir
@callback handle_hover(x :: integer(), y :: integer(), state()) :: event_result()
```

Handles mouse motion with no button held. Requires `:hover` in `:handles`, and the
app to have been started with hover tracking on.

# `handle_key`
*optional* 

```elixir
@callback handle_key(key(), state()) :: event_result()
```

Handles a key press with no modifiers. Requires `:keyboard` in `:handles`.

`key` is the key atom — `:enter`, `:up`, or a printable ASCII character as an atom.
Also receives `{:key, key, []}` events when `c:handle_key/3` is not exported.

# `handle_key`
*optional* 

```elixir
@callback handle_key(key(), modifiers(), state()) :: event_result()
```

Handles a key press with modifiers. Requires `:keyboard` in `:handles`.

`modifiers` is a subset of `[:ctrl, :alt, :shift]` in that order. Takes precedence
over `c:handle_key/2` for every `{:key, key, modifiers}` event, including one with
an empty modifier list.

# `handle_mouse_up`
*optional* 

```elixir
@callback handle_mouse_up(x :: integer(), y :: integer(), state()) :: event_result()
```

Handles a mouse button release, the event that activates a widget. Requires
`:mouse_up` in `:handles`. `x` and `y` are absolute zero-based screen cells.

# `handle_paste`
*optional* 

```elixir
@callback handle_paste(text :: String.t(), state()) :: event_result()
```

Handles pasted text. Requires `:paste` in `:handles`.

`text` has already passed through `Drafter.Clipboard.sanitize/1`.

# `handle_press`
*optional* 

```elixir
@callback handle_press(x :: integer(), y :: integer(), state()) :: event_result()
```

Handles a mouse button press. Requires `:press` (or its alias `:click`) in
`:handles`. `x` and `y` are absolute zero-based screen cells.

# `handle_scroll`
*optional* 

```elixir
@callback handle_scroll(scroll_direction(), state()) :: event_result()
```

Handles a scroll wheel event. Requires `:scroll` in `:handles`.

`direction` is `:up` or `:down`. When `:scroll` is declared but this is not
exported, the router moves `:_scroll_offset` on the state by the scroll config's
`:step` instead.

# `image_active?`
*optional* 

```elixir
@callback image_active?(state()) :: boolean()
```

Whether the widget is drawing a transmitted image right now.

A widget that exports `image/3` but only paints in some of its modes implements this
so the runtime can skip the image pipeline entirely in the others: no per-frame
placement, no generation task, no bytes. Absent, a widget exporting `image/3` counts
as always active.

Called on the current state on every frame, so it must be cheap.

# `mount`

```elixir
@callback mount(props()) :: state()
```

Builds the widget's initial state from its props map.

`props` comes from `from_component_opts/2` for a tag element, or is the map given
directly in a `{Module, props}` element. The returned term is the widget's state,
usually its own struct.

# `render`

```elixir
@callback render(state(), rect()) :: render_result()
```

Draws the widget into `rect`.

`rect` is `%{x: , y: , width: , height: }` in absolute screen cells. Returns a list
of `Drafter.Draw.Strip` structs, one per row starting at `rect.y`, or
`{:error, reason}`. Called on every frame the widget is visible, so it must be
free of side effects.

# `unmount`
*optional* 

```elixir
@callback unmount(state()) :: :ok
```

Releases the widget's resources as it leaves the hierarchy. Returns `:ok`.

# `update`
*optional* 

```elixir
@callback update(props(), state()) :: state()
```

Folds a fresh props map into an existing state when the widget is re-rendered.

Returns the state to keep. A widget that defines no `update/2` keeps its state
unchanged, so state a widget owns and props do not describe — scroll offset, cursor
position, drag state — survives a re-render.

# `apply_props`

```elixir
@spec apply_props(module(), map(), state()) :: state()
```

Applies re-render props to a widget's state, using that widget's own `update/2`.

`module` is the widget module, `props` the map from the new render, `state` the
state as it stands. Returns the state to keep. A module that exports no `update/2`
gets `state` back unchanged, so state the props do not describe — scroll offset,
cursor and drag positions — survives.

Both the in-hierarchy path and the widget's process go through this, so a widget
behaves the same whether or not it runs in its own process.

# `handle_event`

```elixir
@spec handle_event(Drafter.Event.t(), state()) :: event_result()
```

Default `c:handle_event/2`: handles nothing, returning `{:noreply, state}`.

    iex> Drafter.Widget.handle_event({:key, :up}, %{count: 0})
    {:noreply, %{count: 0}}

# `image_active?`

```elixir
@spec image_active?(module(), state()) :: boolean()
```

Whether `module` draws a transmitted image for `state`.

True only for a module exporting `image/3`, and then only when its `c:image_active?/1`
says so; a module without that callback is active whenever it exports `image/3`. The
runtime asks this before placing an image or asking one to be generated, so a widget
in a character-drawing mode costs nothing on the image path.

    iex> Drafter.Widget.image_active?(Drafter.Widget.Label, %{})
    false

    iex> Drafter.Widget.image_active?(Drafter.Widget.Slider, Drafter.Widget.Slider.mount(%{renderer: :text}))
    false

# `mount`

```elixir
@spec mount(props()) :: state()
```

Default `c:mount/1`: ignores the props and returns `%{}`.

    iex> Drafter.Widget.mount(%{count: 3})
    %{}

# `normalize_handles`

```elixir
@spec normalize_handles([atom()]) :: [atom()]
@spec normalize_handles(term()) :: term()
```

The event names a `handles:` list resolves to.

Maps `:click` to `:press`, the name `Drafter.Widget.EventRouter` dispatches on, and
removes duplicates. Every other atom is returned as given. A non-list argument is
returned unchanged.

    iex> Drafter.Widget.normalize_handles([:click, :press, :keyboard])
    [:press, :keyboard]

    iex> Drafter.Widget.normalize_handles([:keyboard, :scroll])
    [:keyboard, :scroll]

    iex> Drafter.Widget.normalize_handles(nil)
    nil

# `render`

```elixir
@spec render(state(), rect()) :: render_result()
```

Default `c:render/2`: draws nothing, returning `[]`.

    iex> Drafter.Widget.render(%{}, %{x: 0, y: 0, width: 10, height: 1})
    []

# `unmount`

```elixir
@spec unmount(state()) :: :ok
```

Default `c:unmount/1`: returns `:ok`.

    iex> Drafter.Widget.unmount(%{count: 0})
    :ok

# `update`

```elixir
@spec update(props(), state()) :: state()
```

Default `c:update/2`: ignores the props and returns `state` unchanged.

    iex> Drafter.Widget.update(%{count: 9}, %{count: 0})
    %{count: 0}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
