Harlock.Elements (harlock v0.2.0)

Copy Markdown View Source

View-tree constructors. Auto-imported into apps via use Harlock.App, so most apps use text(...), vbox(...), box(...) etc. directly without qualification.

An element is a plain struct (Harlock.Element); building a view is just calling these functions to assemble a tree. The renderer walks the tree once per dirty frame and produces a Frame ready for the diff renderer.

Primitives

  • text/2 — single-line text content
  • text_input/1 — single-line editable input (paired with Harlock.TextBuffer)
  • vbox/1 / hbox/1 — vertical / horizontal stacks with layout constraints (:length, :percentage, :fill)
  • box/1 — single-child container with border + title + padding
  • spacer/0 — empty element that occupies a layout slot
  • overlay/1 — render a foreground element on top of a background with optional focus_trap
  • table/1 / list/2 — row-based primitives with selection and focus highlighting
  • column/1 — column spec for table/1

All elements that accept focus take a :focusable opt — the runtime walks the tree to collect focusable ids for Tab traversal.

Summary

Functions

A single-child container with a border and optional inner padding.

Build a column spec for use inside table/1.

Horizontal stack. Children share the box's height; width is split.

Single-column table with chrome hidden. :row_id defaults to & &1 because lists are usually homogeneous; pass an explicit :row_id if yours aren't.

Render over on top of child in a sub-rectangle anchored within the parent region.

Empty cell that occupies a layout slot. Useful with constraints.

Table primitive.

A text element. content is rendered as a single line; callers split multi-line content themselves.

Single-line text input.

Vertical stack. Children share the box's width; height is split according to :constraints.

Functions

box(opts)

@spec box(keyword()) :: Harlock.Element.t()

A single-child container with a border and optional inner padding.

Required options:

  • :child — the element drawn inside the box

Optional:

  • :title — string overlaid on the top border (truncated to fit)
  • :title_align:left (default) | :center | :right

  • :border:single (default) | :double | :rounded | :thick | :none

  • :border_style%Style{} or keyword applied to the border + title
  • :padding — non-negative integer (uniform), {v, h}, or {top, right, bottom, left}
  • :focusable, :focus_style — when focused, the focus style replaces the border style (the child is left alone)

For multiple children, wrap them in vbox/1 or hbox/1 and pass the result as :child. The box reserves one cell on each side for the border (unless :border is :none); when the region is smaller than that the border is skipped and the child takes the full region.

column(opts \\ [])

@spec column(keyword()) :: Harlock.Element.Column.t()

Build a column spec for use inside table/1.

Options:

  • :title — header label
  • :width — layout constraint (default {:fill, 1})
  • :align:left | :right | :center

  • :renderfn row -> string | iodata

hbox(opts \\ [])

@spec hbox(keyword()) :: Harlock.Element.t()

Horizontal stack. Children share the box's height; width is split.

Options as vbox/1.

list(items, opts \\ [])

@spec list(
  Enumerable.t(),
  keyword()
) :: Harlock.Element.t()

Single-column table with chrome hidden. :row_id defaults to & &1 because lists are usually homogeneous; pass an explicit :row_id if yours aren't.

Options:

overlay(opts)

@spec overlay(keyword()) :: Harlock.Element.t()

Render over on top of child in a sub-rectangle anchored within the parent region.

Required options:

  • :child — the background element (rendered first)
  • :over — the foreground element (rendered on top)

Anchor + sizing:

  • :anchor:center (default), :top_left, :top_right, :bottom_left, :bottom_right, or {row, col} for absolute placement
  • :width — width of the over region in cells (default: full parent)
  • :height — height of the over region in cells (default: full parent)

Focus:

  • :focus_trap — when true, focus traversal wraps within the over subtree until the overlay disappears. Prior focus is stashed and restored automatically when the overlay closes.

Overlays nest cleanly: just put another overlay as :over.

spacer()

@spec spacer() :: Harlock.Element.t()

Empty cell that occupies a layout slot. Useful with constraints.

table(opts)

@spec table(keyword()) :: Harlock.Element.t()

Table primitive.

Required options:

  • :columns — list of column/1 specs
  • :rows — enumerable of row data
  • :row_id — fn(row) -> id. Row identity is by id, not index, so focus and selection survive sort/filter.

Optional:

  • :focused_row — currently-focused row id
  • :selection:none | {:single, id} | {:multi, MapSet}

  • :show_header — default true
  • :focusable, :focus_trap — same as other elements

text(content, opts \\ [])

@spec text(
  String.t(),
  keyword()
) :: Harlock.Element.t()

A text element. content is rendered as a single line; callers split multi-line content themselves.

Options:

  • :style%Harlock.Render.Style{} or keyword list of style attrs.

text_input(opts)

@spec text_input(keyword()) :: Harlock.Element.t()

Single-line text input.

Required options:

  • :value — the current string contents (caller-owned)
  • :cursor — grapheme index of the cursor (0..length)
  • :focusable — id for focus traversal

Optional:

  • :placeholder — shown when value is empty and the input isn't focused
  • :max_length — soft hint; the element doesn't enforce it, but Harlock.TextBuffer.apply_key/3 respects it if you wire it in your app
  • :style%Style{} for the value text
  • :placeholder_style%Style{} for the placeholder
  • :password — when true, render each grapheme as

The element is a dumb renderer. The app's update/2 owns the value and cursor; call Harlock.TextBuffer.apply_key/3 to react to key events when this input is focused. When focused, the renderer positions the terminal cursor at the visual column matching :cursor.

vbox(opts \\ [])

@spec vbox(keyword()) :: Harlock.Element.t()

Vertical stack. Children share the box's width; height is split according to :constraints.

Options:

  • :constraints — list of layout constraints, one per child. Defaults to [fill: 1] for each child if not provided.
  • :children — list of child elements.