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

Behaviour for composable widget traits.

Traits are reusable capabilities that can be composed onto widgets.
Each trait manages its own slice of state, handles specific events,
and can decorate rendering via pre/post render hooks.

A widget opts into traits with `use Drafter.Widget, traits: [...]`; the functions
here are what that macro calls to turn the trait list into handles, default state,
a capability bitmap, and the scroll configuration.

## Built-in trait names

`resolve_module/1` maps these atoms to modules; any other atom is taken to be a
module name already.

  * `:focusable` - `Drafter.Widget.Trait.Focusable`
  * `:scrollable` - `Drafter.Widget.Trait.Scrollable`
  * `:selectable` - `Drafter.Widget.Trait.Selectable`
  * `:editable` - `Drafter.Widget.Trait.Editable`
  * `:collapsible` - `Drafter.Widget.Trait.Collapsible`
  * `:resizable` - `Drafter.Widget.Trait.Resizable`
  * `:draggable` - `Drafter.Widget.Trait.Draggable`
  * `:animatable` - `Drafter.Widget.Trait.Animatable`

## Optional callbacks and their defaults

A trait that omits an optional callback is treated as follows:

  * `c:dependencies/0` - no dependencies
  * `c:handles/0` - handles nothing
  * `c:render_affecting_fields/0` - every key of `c:default_state/0`
  * `c:layout_static?/0` - `true`

# `event_result`

```elixir
@type event_result() ::
  {:ok, trait_state()} | {:pass, trait_state()} | {:consume, trait_state()}
```

# `trait_name`

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

# `trait_state`

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

# `default_state`

```elixir
@callback default_state() :: trait_state()
```

# `dependencies`
*optional* 

```elixir
@callback dependencies() :: [trait_name()]
```

# `handle_event`
*optional* 

```elixir
@callback handle_event(term(), trait_state(), widget_state :: map()) :: event_result()
```

# `handles`
*optional* 

```elixir
@callback handles() :: [atom()]
```

# `layout_static?`
*optional* 

```elixir
@callback layout_static?() :: boolean()
```

# `name`

```elixir
@callback name() :: trait_name()
```

# `post_render`
*optional* 

```elixir
@callback post_render(
  [Drafter.Draw.Strip.t()],
  trait_state(),
  widget_state :: map(),
  rect :: map()
) :: [
  Drafter.Draw.Strip.t()
]
```

# `pre_render`
*optional* 

```elixir
@callback pre_render(trait_state(), widget_state :: map(), rect :: map()) ::
  {trait_state(), map()}
```

# `render_affecting_fields`
*optional* 

```elixir
@callback render_affecting_fields() :: [atom()]
```

# `all_layout_static?`

```elixir
@spec all_layout_static?([module()]) :: boolean()
```

Whether every trait reports `c:layout_static?/0` as `true`, treating a trait that
does not export it as `true`. An empty list is static.

    iex> Drafter.Widget.Trait.all_layout_static?([Drafter.Widget.Trait.Focusable])
    true

    iex> Drafter.Widget.Trait.all_layout_static?([])
    true

# `any_focusable?`

```elixir
@spec any_focusable?([module()]) :: boolean()
```

Whether any trait reports the name `:focusable`, which is what makes the widget
take part in tab order.

    iex> Drafter.Widget.Trait.any_focusable?([Drafter.Widget.Trait.Focusable])
    true

    iex> Drafter.Widget.Trait.any_focusable?([Drafter.Widget.Trait.Scrollable])
    false

# `build_bitmap`

```elixir
@spec build_bitmap([module()]) :: non_neg_integer()
```

The capability bitmap for a trait list, for use with `handles_event?/2`.

Ors together the bit of every handle the traits collect, plus the focus bit when
any trait is focusable. A handle with no bit assigned contributes nothing.

    iex> Drafter.Widget.Trait.build_bitmap([])
    0

    iex> bitmap = Drafter.Widget.Trait.build_bitmap([Drafter.Widget.Trait.Scrollable])
    iex> {Drafter.Widget.Trait.handles_event?(bitmap, :scroll), Drafter.Widget.Trait.handles_event?(bitmap, :drag)}
    {true, false}

# `collect_handles`

```elixir
@spec collect_handles([module()]) :: [atom()]
```

The deduplicated union of every trait's `c:handles/0`, in trait order.

A trait that does not export `c:handles/0` contributes nothing.

    iex> Drafter.Widget.Trait.collect_handles([Drafter.Widget.Trait.Focusable])
    [:focus, :blur]

    iex> Drafter.Widget.Trait.collect_handles([])
    []

# `collect_render_affecting_fields`

```elixir
@spec collect_render_affecting_fields([module()]) :: [atom()]
```

The deduplicated union of every trait's `c:render_affecting_fields/0`, in trait
order.

A trait that does not export it contributes the keys of its
`c:default_state/0` instead.

    iex> Drafter.Widget.Trait.collect_render_affecting_fields([Drafter.Widget.Trait.Focusable])
    [:focused]

# `handles_event?`

```elixir
@spec handles_event?(non_neg_integer(), atom()) :: boolean()
```

Whether a bitmap built by `build_bitmap/1` carries the bit for `event_type`.

Recognised event types are `:scroll`, `:keyboard`, `:char`, `:click`, `:drag`,
`:hover`, `:press`, `:mouse_up`, `:focus` and `:blur`. `:focus` and `:blur` share
one bit, so a bitmap that answers `true` for either answers `true` for both. Any
other atom is always `false`.

    iex> bitmap = Drafter.Widget.Trait.build_bitmap([Drafter.Widget.Trait.Focusable])
    iex> {Drafter.Widget.Trait.handles_event?(bitmap, :focus), Drafter.Widget.Trait.handles_event?(bitmap, :blur)}
    {true, true}

    iex> Drafter.Widget.Trait.handles_event?(0, :keyboard)
    false

    iex> Drafter.Widget.Trait.handles_event?(0xFFFF, :unknown_event)
    false

# `merge_default_states`

```elixir
@spec merge_default_states([module()]) :: map()
```

Every trait's `c:default_state/0` merged left to right, so a later trait wins a
key clash.

    iex> Drafter.Widget.Trait.merge_default_states([Drafter.Widget.Trait.Focusable])
    %{focused: false}

    iex> Drafter.Widget.Trait.merge_default_states([])
    %{}

# `resolve_all`

```elixir
@spec resolve_all(
  [atom() | {atom(), keyword()} | module()],
  keyword()
) :: [module()]
```

Expands trait specs into the full deduplicated list of trait modules.

Each spec is a trait name, a module, or a `{name, opts}` pair whose options are
discarded. Every trait's `c:dependencies/0` are pulled in transitively and appear
before the trait that asked for them. The `opts` argument is ignored.

    iex> Drafter.Widget.Trait.resolve_all([:focusable, :scrollable], [])
    [Drafter.Widget.Trait.Focusable, Drafter.Widget.Trait.Scrollable]

    iex> Drafter.Widget.Trait.resolve_all([{:focusable, step: 2}, :focusable], [])
    [Drafter.Widget.Trait.Focusable]

# `resolve_module`

```elixir
@spec resolve_module(atom() | module()) :: module()
```

The module behind a trait name.

Returns the built-in module for one of the names listed in the module doc, and
`name` itself for anything else — no check is made that the result is a module.

    iex> Drafter.Widget.Trait.resolve_module(:focusable)
    Drafter.Widget.Trait.Focusable

    iex> Drafter.Widget.Trait.resolve_module(:not_a_trait)
    :not_a_trait

# `scroll_config`

```elixir
@spec scroll_config(
  [module()],
  keyword()
) :: map() | nil
```

The scroll configuration for a trait-mode widget, or `nil` when
`Drafter.Widget.Trait.Scrollable` is not among its traits.

Reads `opts[:scroll]`, itself defaulting to `[]`:

  * `:direction` - `:vertical | :horizontal`. Default `:vertical`.
  * `:step` - `t:pos_integer/0` rows per wheel notch. Default `1`.
  * `:show_scrollbar` - `:auto | true | false`. Default `:auto`.

These differ from the handles-mode `:scroll` defaults documented on
`Drafter.Widget`, which are `:horizontal` and a step of `5`.

    iex> Drafter.Widget.Trait.scroll_config([Drafter.Widget.Trait.Scrollable], [])
    %{direction: :vertical, step: 1, show_scrollbar: :auto}

    iex> Drafter.Widget.Trait.scroll_config([Drafter.Widget.Trait.Scrollable], scroll: [step: 4])
    %{direction: :vertical, step: 4, show_scrollbar: :auto}

    iex> Drafter.Widget.Trait.scroll_config([Drafter.Widget.Trait.Focusable], [])
    nil

---

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