Drafter.Widget.Container (drafter v0.3.2)

Copy Markdown View Source

Holds and arranges child widgets using vertical, horizontal, or stack layouts.

In :vertical layout children share height equally; in :horizontal they share width equally. :stack overlays all children at the same position, rendering only the last child's output. Events are forwarded to every child on each dispatch.

Children are widget modules, not element tuples. Each is mounted during mount/1 and its state is retained across renders.

Component tag

This module has no component_tag/0 and no Drafter.App helper. It is used by placing it in a render tree as a {module, props} pair:

{Drafter.Widget.Container,
 %{
   layout: :vertical,
   children: [
     {Drafter.Widget.Label, %{text: "Top section"}},
     {Drafter.Widget.Label, %{text: "Bottom section"}}
   ]
 }}

The vertical/2 and horizontal/2 helpers in Drafter.App do not build this widget — they produce {:layout, direction, children, opts} elements that the component renderer lays out itself.

Options

  • :children - list of {module, props} or {module, props, state} tuples. Default []. A two-element pair is mounted; a three-element one is taken as already mounted
  • :layout - arrangement: :vertical (default), :horizontal, :stack
  • :padding - inner padding in columns and rows. Default 0
  • :border_style - :none (default) or any other atom. Any value other than :none insets the content rect by one cell on every side; no border characters are drawn
  • :style - map/0 of style properties. Default %{}. Carried on the state and not consulted while rendering

update/2 re-reads every option. Supplying :children re-mounts each entry given as a {module, props} pair, discarding whatever state that child had accumulated; pass {module, props, state} triples to keep it.

Widget value

Drafter.get_widget_value/1 is not implemented for this widget and returns nil.

Usage

Drafter.Widget.Container.mount(%{
  layout: :horizontal,
  padding: 1,
  children: [
    {Drafter.Widget.Label, %{text: "Left"}},
    {Drafter.Widget.Label, %{text: "Right"}}
  ]
})

Summary

Functions

Offers the event to every child in order.

Builds the container state from props, mounting every {module, props} child and leaving {module, props, state} triples alone.

Builds a container struct directly from children and opts.

Renders every child into its share of rect and stacks the results.

Calls unmount/1 on every child that exports it and returns :ok.

Folds fresh props into state.

Types

child_spec()

layout_type()

@type layout_type() :: :vertical | :horizontal | :stack

t()

@type t() :: %Drafter.Widget.Container{
  border_style: atom(),
  children: [child_spec()],
  layout: layout_type(),
  padding: non_neg_integer(),
  style: map()
}

Functions

focused(state)

handle_event(event, state)

@spec handle_event(Drafter.Event.t(), t()) :: {:ok, t()} | {:bubble, t()}

Offers the event to every child in order.

Every child sees the event, even after an earlier one handled it. A child returning {:ok, _} or {:ok, _, _} marks the event handled; {:bubble, _} and {:noreply, _} keep the child's new state without marking it handled, and any other return leaves that child's state alone. Returns {:ok, state} if any child handled the event and {:bubble, state} otherwise; child states are updated either way. Actions returned by children are discarded.

mount(props)

@spec mount(Drafter.Widget.props()) :: t()

Builds the container state from props, mounting every {module, props} child and leaving {module, props, state} triples alone.

iex> c = Drafter.Widget.Container.mount(%{children: [{Drafter.Widget.Label, %{text: "Hi"}}]})
iex> [{module, _props, child_state}] = c.children
iex> {module, child_state.text, c.layout}
{Drafter.Widget.Label, "Hi", :vertical}

iex> c = Drafter.Widget.Container.mount(%{})
iex> {c.children, c.layout, c.padding, c.border_style, c.style}
{[], :vertical, 0, :none, %{}}

new(children, opts \\ [])

@spec new(
  [child_spec()],
  keyword()
) :: t()

Builds a container struct directly from children and opts.

Unlike mount/1 this stores children exactly as given, so a {module, props} pair is not mounted and render/2 will fail on it. Use mount/1 unless the children are already {module, props, state} triples.

Options: :layout (default :vertical), :padding (default 0), :border_style (default :none), :style (default %{}).

iex> c = Drafter.Widget.Container.new([], layout: :horizontal, padding: 2)
iex> {c.children, c.layout, c.padding, c.border_style, c.style}
{[], :horizontal, 2, :none, %{}}

preferred_height(args, opts)

render(state, rect)

@spec render(t(), Drafter.Widget.rect()) :: [Drafter.Draw.Strip.t()]

Renders every child into its share of rect and stacks the results.

The content rect is inset by :padding, plus one further cell on each side when :border_style is not :none. :vertical gives each child div(height, child_count) rows, :horizontal gives each div(width, child_count) columns, and :stack gives every child the full rect but returns only the last child's strips. Returns exactly rect.height strips, each padded to rect.width. A child returning {:error, reason} contributes no rows.

unmount(state)

@spec unmount(t()) :: :ok

Calls unmount/1 on every child that exports it and returns :ok.

update(props, state)

@spec update(Drafter.Widget.props(), t()) :: t()

Folds fresh props into state.

Re-reads :layout, :padding, :border_style and :style. When :children is present the whole child list is rebuilt, mounting each {module, props} pair afresh, so any state those children held is lost.

iex> c = Drafter.Widget.Container.mount(%{layout: :vertical})
iex> Drafter.Widget.Container.update(%{layout: :stack, padding: 1}, c).layout
:stack

update_props_from_mount(mount_props, existing_state, opts)