Protocol for user-defined widgets that compose ExRatatui primitives.
Implement this protocol on your own struct to build custom widgets in
pure Elixir — no Rust required. render/2 receives the widget struct
and the %ExRatatui.Layout.Rect{} it should occupy, and returns a
list of placed children. Children may be primitives
(%ExRatatui.Widgets.Paragraph{}, %ExRatatui.Widgets.Block{}, …)
or other custom widgets — the render tree is expanded recursively
before crossing the NIF boundary.
The protocol is intentionally minimal and stateless: the struct
carries all data the render needs. State that evolves over time
(keyboard focus, selection, input buffers) lives in your
ExRatatui.App / ExRatatui.Session model and is projected onto a
fresh struct each frame.
Example
defmodule MyApp.Widgets.UserCard do
defstruct [:user, selected?: false]
defimpl ExRatatui.Widget do
alias ExRatatui.Layout
alias ExRatatui.Layout.Rect
alias ExRatatui.Style
alias ExRatatui.Widgets.{Block, Paragraph}
def render(%{user: user, selected?: sel?}, %Rect{} = rect) do
border_style = if sel?, do: %Style{fg: :yellow}, else: %Style{}
[header, body] =
Layout.split(rect, :vertical, [{:length, 1}, {:min, 0}])
[
{%Block{title: user.name, borders: [:all], border_style: border_style}, rect},
{%Paragraph{text: user.handle, style: %Style{modifiers: [:bold]}}, header},
{%Paragraph{text: user.bio}, body}
]
end
end
endThen draw it like any other widget:
ExRatatui.draw(terminal, [{%MyApp.Widgets.UserCard{user: u}, rect}])Composition
A custom widget can return another custom widget in its children list. The expander walks recursively until every entry is a primitive, with a safety cap of 32 nesting levels.
Limitations
Custom widgets are expanded at the top level of
ExRatatui.draw/2's widget list. They are not currently supported
inside ExRatatui.Widgets.Popup's :content field or
ExRatatui.Widgets.WidgetList's :items — those fields still
require primitive widgets. A custom widget can itself return a
%Popup{} or %WidgetList{}; the restriction only applies to the
widgets placed inside them.
Summary
Functions
Renders this widget at the given rect into a list of placed children.
Types
@type rendered() :: {ExRatatui.widget(), ExRatatui.Layout.Rect.t()}
@type t() :: term()
All the types that implement this protocol.
Functions
@spec render(t(), ExRatatui.Layout.Rect.t()) :: [rendered()]
Renders this widget at the given rect into a list of placed children.
Children may be primitive widgets or other structs implementing
ExRatatui.Widget. Return [] to render nothing.