Drafter.Widget.Footer (drafter v0.3.2)

Copy Markdown View Source

Renders a single-row key-binding bar, typically anchored to the bottom of a screen.

Bindings are {key_label, description} tuples displayed as styled [key] action pairs separated by a configurable separator string. When no :bindings list is provided the widget calls keybindings/0 on the currently active screen module automatically.

Each binding is clickable: hovering highlights the binding, and clicking dispatches the associated key event as if the user pressed that key.

Component tag

Tag :footer, built by Drafter.App as {:footer, opts}:

footer(opts)

There is no positional argument; every prop comes from opts. :app_module is supplied by the renderer.

Options

  • :bindings - list of {key, description} tuples. Default nil, in which case the widget calls keybindings/0 on the active screen module, falling back to :app_module, and then to [] when neither exports it
  • :separator - String.t/0 placed between binding pairs. Default " "
  • :style - style map applied to description text. Default nil, which uses the computed :footer theme style
  • :key_style - style map applied to key label text. Default nil, which uses the computed :footer :key part style
  • :app_module - module used for theme resolution and as the fallback source of keybindings/0, passed by the renderer as :__app_module__. Default nil

update/2 re-reads every option, and passing bindings: nil explicitly restores the "ask the active screen" behaviour. All of them are live-updatable through the component tree.

Widget value

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

Events

The footer is not focusable and handles no keys. A hover moves the highlight to the binding under the pointer, and a mouse release on a binding sends the corresponding key event through Drafter.Event.Manager. A label of one byte becomes a {:char, codepoint} event, a label in the built-in table becomes its {:key, atom} event, a label with + becomes {:key, key, modifiers}, and any other label is downcased and converted to an atom.

Usage

footer(bindings: [{"q", "Quit"}, {"Tab", "Focus next"}, {"Enter", "Select"}])
footer()

Summary

Functions

The registry tag for this widget.

Turns the {:footer, opts} element into a props map for mount/1.

Moves the highlight to the binding spanning column x, counted from the left edge of the widget.

Dispatches the key event for the binding under column x.

Builds the footer state from props. :hovered_index always starts at nil.

Always 1: the footer occupies a single row.

Draws the binding bar as a single strip padded or cropped to rect.width.

Callback implementation for Drafter.Widget.unmount/1.

Folds fresh props into state, re-reading :bindings, :style, :key_style, :separator and :app_module. :hovered_index is left alone.

Returns mount_props unchanged, so a re-render passes every option through to update/2.

Types

binding()

@type binding() :: {String.t(), String.t()}

t()

@type t() :: %Drafter.Widget.Footer{
  app_module: module() | nil,
  bindings: [binding()] | nil,
  hovered_index: non_neg_integer() | nil,
  key_style: map() | nil,
  separator: String.t(),
  style: map() | nil
}

Functions

component_tag()

@spec component_tag() :: :footer

The registry tag for this widget.

iex> Drafter.Widget.Footer.component_tag()
:footer

focused(state)

from_component_opts(args, opts)

@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()

Turns the {:footer, opts} element into a props map for mount/1.

The positional argument is ignored. :__app_module__ becomes :app_module.

iex> Drafter.Widget.Footer.from_component_opts(nil, bindings: [{"q", "Quit"}])
%{bindings: [{"q", "Quit"}], separator: " ", style: nil, key_style: nil, app_module: nil}

handle_event(event, state)

Callback implementation for Drafter.Widget.handle_event/2.

handle_hover(x, y, state)

@spec handle_hover(integer(), integer(), t()) :: {:ok, t()} | {:noreply, t()}

Moves the highlight to the binding spanning column x, counted from the left edge of the widget.

Returns {:ok, state} with the new :hovered_index, which is nil when x falls on a separator or past the last binding, or {:noreply, state} when the index has not changed.

handle_mouse_up(x, y, state)

@spec handle_mouse_up(integer(), integer(), t()) :: {:ok, t()} | {:noreply, t()}

Dispatches the key event for the binding under column x.

On a hit the event is sent through Drafter.Event.Manager.send_event/1, exactly as if the key had been pressed, and {:ok, state} is returned. A release on a separator or past the last binding returns {:noreply, state}.

mount(props)

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

Builds the footer state from props. :hovered_index always starts at nil.

iex> f = Drafter.Widget.Footer.mount(%{bindings: [{"q", "Quit"}]})
iex> {f.bindings, f.separator, f.style, f.key_style, f.hovered_index}
{[{"q", "Quit"}], " ", nil, nil, nil}

preferred_height(args, opts)

@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()

Always 1: the footer occupies a single row.

render(state, rect)

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

Draws the binding bar as a single strip padded or cropped to rect.width.

Each binding takes " key " followed by " description", with :separator between pairs and none after the last. The hovered binding is drawn with reverse: true. rect.height is not consulted.

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

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

Folds fresh props into state, re-reading :bindings, :style, :key_style, :separator and :app_module. :hovered_index is left alone.

:bindings is taken whenever the key is present, so bindings: nil restores the active-screen lookup rather than being ignored.

iex> f = Drafter.Widget.Footer.mount(%{bindings: [{"q", "Quit"}]})
iex> Drafter.Widget.Footer.update(%{bindings: nil}, f).bindings
nil

update_props_from_mount(mount_props, existing_state, opts)

@spec update_props_from_mount(Drafter.Widget.props(), t(), keyword()) ::
  Drafter.Widget.props()

Returns mount_props unchanged, so a re-render passes every option through to update/2.