API Reference
July 11, 2026 · View on GitHub
Complete reference for all public types, functions, and constants in maya.
Table of Contents
- App Framework (run, Program, Cmd, Sub, key_map)
- DSL Nodes
- DSL Style Tags
- DSL Layout Tags
- DSL Runtime Pipes
- DSL Factory Functions
- DSL Constants
- Element Types
- Style System
- Color
- Theme
- Border
- Layout Types
- Event System
- Event Predicates
- Signals
- Canvas
- Widgets
- Core Types
App Framework
run<P>()
template <Program P>
void run(RunConfig cfg = {});
The primary entry point for interactive apps. P must satisfy the Program
concept (see below). The runtime owns the event loop, calling P::update on
each message and re-rendering via P::view.
Program concept
template <typename P>
concept Program = requires {
typename P::Model;
typename P::Msg;
} && (HasFullInit<P> || HasSimpleInit<P>)
&& requires(P::Model m, P::Msg msg) {
{ P::update(std::move(m), std::move(msg)) } -> std::convertible_to<std::pair<P::Model, Cmd<P::Msg>>>;
} && requires(const P::Model& m) {
{ P::view(m) } -> std::convertible_to<Element>;
};
A Program is a struct with:
Model— the app state type (plain data)Msg— astd::variantof all possible messagesinit()— returnsModel(simple) orstd::pair<Model, Cmd<Msg>>(full)update(Model, Msg)— returnsstd::pair<Model, Cmd<Msg>>view(const Model&)— returnsElementsubscribe(const Model&)(optional) — returnsSub<Msg>
Cmd<Msg>
template <typename Msg>
class Cmd {
static Cmd none();
static Cmd quit();
static Cmd batch(std::vector<Cmd> cmds); // also variadic
static Cmd after(std::chrono::milliseconds delay, Msg msg);
static Cmd task(std::function<void(std::function<void(Msg)>)> fn);
static Cmd task_isolated(...); // detached thread
static Cmd set_title(std::string title);
static Cmd write_clipboard(std::string text);
static Cmd query_clipboard();
// Interactive-child escape hatch (see below).
template <std::invocable F> static Cmd suspend(F&& run);
// Inline scrollback control (see docs/internals/witness-chain.md).
static Cmd commit_scrollback(ScrollbackDebt debt);
static Cmd commit_scrollback_overflow();
static Cmd force_redraw();
static Cmd reset_inline();
};
Commands represent side effects. Cmd<Msg>{} (or Cmd::none()) means no
effect. Cmd::quit() exits the app. Cmd::batch() combines multiple commands.
Cmd::after() sends a delayed message. Cmd::task() runs an async function
that may produce a message.
Cmd::query_clipboard() / write_clipboard() — clipboard I/O over the escape channel
static Cmd write_clipboard(std::string text); // put text on the clipboard
static Cmd query_clipboard(); // read clipboard → PasteEvent
Both travel in-band over the terminal escape channel, so they work across
SSH with no remote clipboard tool. write_clipboard() emits OSC 52.
query_clipboard() asks the terminal to send its clipboard back; the reply
arrives as a PasteEvent (matched by pasted() / Sub::on_paste).
Maya picks the read protocol from the host: OSC 52 (text-only) by default, or
kitty's OSC 5522 multi-format read when a kitty host is detected
(KITTY_WINDOW_ID set, or kitty-like TERM — both survive an sshd hop). Only
OSC 5522 can carry image bytes, which is what enables screenshot paste
over SSH: the decoded image is delivered as one PasteEvent whose content
holds the raw bytes. See Events → Clipboard reads and image paste over
SSH.
Cmd::suspend() — hand the real terminal to an interactive child
template <std::invocable F>
requires std::convertible_to<std::invoke_result_t<F>, Msg>
static Cmd suspend(F&& run);
Suspends the TUI and hands the real terminal to run — the escape hatch
for interactive children (sudo password prompts, $EDITOR, pagers). The
runtime tears the TUI down to a clean, cooked (line-disciplined) tty, calls
run() synchronously on the UI thread — the user is interacting with the
child, so there is nothing else to do — then restores raw mode + the TUI
escapes, re-anchors the renderer (inline: a fresh serialize below the child's
output; fullscreen: a full repaint), and dispatches the Msg that run
returned so update() can fold the child's result back into the model.
run returning a Msg (rather than void) is what closes the loop: the
callable typically spawns the child with inherited stdio, tees its output to a
buffer, and returns a completion Msg carrying the exit code + captured bytes.
return Cmd<Msg>::suspend([] {
int rc = std::system("sudo -v"); // child owns the tty here
return Msg{SudoDone{ .code = rc }}; // folded back after restore
});
See the Cmd::suspend demo in d726f07 and App::suspend in
maya/app/app.hpp.
Sub<Msg>
template <typename Msg>
class Sub {
static Sub none();
static Sub batch(std::vector<Sub> subs); // also variadic: batch(a, b, c)
static Sub on_key(std::function<std::optional<Msg>(const KeyEvent&)> fn);
static Sub on_mouse(std::function<std::optional<Msg>(const MouseEvent&)> fn);
static Sub on_resize(std::function<Msg(Size)> fn);
static Sub on_paste(std::function<Msg(std::string)> fn);
static Sub every(std::chrono::milliseconds interval, Msg msg);
static Sub on_animation_frame(Msg msg); // sugar for every(16ms, msg)
};
Subscriptions declare which external events the app listens to. Returned from
the optional subscribe(const Model&) method. Subscriptions are re-evaluated
when the model changes, enabling conditional subscriptions (e.g. only tick
while a timer is running).
on_animation_frame() is every(16ms, msg) under one shared timer engine —
there is no separate animation pump. Drop the subscription from subscribe()
and the ticks stop; the loop returns to idle wait with zero bytes per frame.
Animation-frame requests
// maya/app/app.hpp
void request_animation_frame() noexcept;
The redraw-only counterpart to Sub::on_animation_frame. A widget calls it
from its build() each frame it wants to keep animating; the run loop folds
these requests into the same wake schedule as Sub::Every timers. The
distinction is intent, not mechanism: Every delivers a Msg (drives
update → model), a frame request only asks for a repaint — pure visual
layer (cursor blink, scramble caret, fade) that reads wall-clock in build()
and mutates nothing. A widget that stops calling drops out of the next
collection and the loop idles. Idempotent within a frame.
key_map<Msg>() and key predicates
using KeySpec = std::variant<char, SpecialKey>;
template <typename Msg>
Sub<Msg> key_map(std::initializer_list<std::pair<KeySpec, Msg>> entries);
// Pure predicates for use inside subscribe() / on_key filters:
bool key_is(const KeyEvent& k, char c) noexcept; // also char32_t / SpecialKey
bool ctrl_is(const KeyEvent& k, char c) noexcept; // Ctrl+c, not Ctrl+Alt+c
bool alt_is(const KeyEvent& k, char c) noexcept; // Alt+c, not Ctrl+Alt+c
key_map() builds a Sub::on_key from a declarative key→message table:
return key_map<Msg>({
{'q', Quit{}}, {'+', Increment{}},
{SpecialKey::Up, Increment{}},
});
The *_is predicates are the building blocks for hand-written on_key
filters where a table isn't expressive enough (modifiers, ranges).
RunConfig
struct RunConfig {
std::string_view title = "";
int fps = 0; // 0 = event-driven
bool mouse = false;
Mode mode = Mode::Fullscreen;
Theme theme = theme::dark;
};
run() — Simple Convenience API
template <SimpleEventFn EventFn, SimpleRenderFn RenderFn>
void run(RunConfig cfg, EventFn&& event_fn, RenderFn&& render_fn);
template <SimpleEventFn EventFn, SimpleRenderFn RenderFn>
void run(EventFn&& event_fn, RenderFn&& render_fn); // default RunConfig
A closure-based entry point that requires no boilerplate. Suitable for most
interactive apps that don't need the full Elm-architecture of run<P>().
Event function: (const Event&) -> bool (returning false quits) or
(const Event&) -> void (call quit() to exit).
Render function: () -> Element or (const Ctx&) -> Element.
Global control — quit() / set_mouse()
// maya/app/quit.hpp
void quit() noexcept; // request a clean exit from run() / live()
void set_mouse(bool on) noexcept; // toggle mouse capture at runtime
set_mouse() flips terminal mouse reporting on/off while the app runs — off
hands the scroll wheel back to the terminal (native scrollback), on recaptures
clicks/drag/wheel. The request is applied on the next loop iteration and keeps
the runtime's mouse state in sync for a clean terminal restore on exit. Works
from run() event functions and from a Program's update()/subscribe().
For Program apps Cmd<Msg>::quit() is preferred over quit(); there is no
Cmd form of set_mouse() yet, so call the free function. See
Events → Mouse capture vs. native terminal scroll.
Ctx
struct Ctx {
Size size; // Current terminal dimensions
Theme theme; // Active color theme
};
The Ctx overload of the render function is useful for adaptive layouts that
need to inspect the terminal size or current theme at render time.
live()
template <AnyLiveRenderFn RenderFn>
void live(LiveConfig cfg, RenderFn&& render_fn);
LiveConfig
struct LiveConfig {
int fps = 30;
int max_width = 0; // 0 = auto-detect
bool cursor = false;
};
canvas_run()
Status canvas_run(
CanvasConfig cfg,
std::function<void(StylePool&, int w, int h)> on_resize,
std::function<bool(const Event&)> on_event,
std::function<void(Canvas&, int w, int h)> on_paint);
CanvasConfig
struct CanvasConfig {
int fps = 60;
bool mouse = false;
Mode mode = Mode::Fullscreen;
std::string title;
};
print()
void print(const Element& root);
void print(const Element& root, int width);
quit()
void quit() noexcept; // Schedule clean exit after current frame
In Program apps, prefer
Cmd<Msg>::quit()fromupdate()instead of callingquit()directly. The free function is still available forlive()andcanvas_run()apps.
DSL Nodes
All nodes satisfy the Node concept:
template <typename T>
concept Node = requires(const T& n) {
{ n.build() } -> std::convertible_to<Element>;
};
template <typename T>
concept DslChild = Node<T> || ElementRange<T>;
DslChild is what v() and h() accept — either a Node (compile-time or
runtime) or an ElementRange (e.g. std::vector<Element>).
TextNode
template <Str S, CTStyle Sty = CTStyle{}>
struct TextNode {
Element build() const;
};
Created via t<"...">.
RuntimeTextNode
template <typename S>
struct RuntimeTextNode {
S content;
Style style{};
TextWrap wrap{TextWrap::Wrap};
operator Element() const;
Element build() const;
};
Created via text(). Supports | Bold, | Fg<R,G,B>, etc.
BoxNode
template <FlexDirection Dir, BoxCfg Cfg, typename... Children>
struct BoxNode {
std::tuple<Children...> children;
Element build() const;
};
Created via v() and h(). Supports layout pipes.
DynNode
template <typename F>
struct DynNode {
F fn;
Element build() const; // Calls fn()
};
Created via dyn().
MapNode
template <typename R, typename Proj>
struct MapNode {
R range;
Proj proj;
Element build() const; // Iterates range, applies proj
};
Created via map().
SpacerNode
struct SpacerNode {
operator Element() const;
Element build() const; // BoxElement with grow=1
};
SepNode
struct SepNode {
operator Element() const;
Element build() const; // Horizontal separator line
};
VSepNode
struct VSepNode {
operator Element() const;
Element build() const; // Vertical separator line
};
BlankNode
struct BlankNode {
operator Element() const;
Element build() const; // Empty TextElement
};
DSL Style Tags
inline constexpr StyTag<...> Bold;
inline constexpr StyTag<...> Dim;
inline constexpr StyTag<...> Italic;
inline constexpr StyTag<...> Underline;
inline constexpr StyTag<...> Strike;
inline constexpr StyTag<...> Inverse;
template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr StyTag<...> Fg;
template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr StyTag<...> Bg;
DSL Layout Tags
template <int T, int R = T, int B = T, int L = R>
inline constexpr PadTag<T,R,B,L> pad;
template <int G>
inline constexpr GapTag<G> gap_;
template <BorderStyle BS>
inline constexpr BorderTag<BS> border_;
template <uint8_t R, uint8_t G, uint8_t B>
inline constexpr BColTag<R,G,B> bcol; // Requires border first
template <int G = 1>
inline constexpr GrowTag<G> grow_;
template <int W>
inline constexpr WidthTag<W> w_; // fixed width; also wraps text nodes
template <int H>
inline constexpr HeightTag<H> h_; // fixed height
DSL Runtime Pipes
Runtime pipe tags for dynamic values. Same | syntax as compile-time pipes.
Layout Pipes
| Function | Tag Type | Description |
|---|---|---|
padding(int) / padding(int,int) / padding(int,int,int,int) | RPad | Runtime padding |
gap(int) | RGap | Gap between children |
margin(int) / margin(int,int) / margin(int,int,int,int) | RMargin | Outer margin |
grow(float g = 1.0f) | RGrow | Flex grow factor |
width(int) | RWidth | Fixed width |
height(int) | RHeight | Fixed height |
Border Pipes
| Function | Tag Type | Description |
|---|---|---|
border(BorderStyle) | RBorder | Border style |
bcolor(Color) | RBCol | Border color |
btext(string, pos = Top, align = Start) | RBText | Border text label (pos/align default) |
Style Pipes
| Function | Tag Type | Description |
|---|---|---|
fgc(Color) | RFg | Foreground color |
bgc(Color) | RBg | Background color |
Alignment Pipes
| Function | Tag Type | Description |
|---|---|---|
align(Align) | RAlign | Cross-axis alignment |
justify(Justify) | RJust | Main-axis distribution |
overflow(Overflow) | ROvf | Overflow behavior |
Scroll Pipes
Wrap content in a scroll viewport backed by a caller-owned ScrollState. The
renderer applies the scroll offset and writes max_x/max_y back after layout
so clamping is automatic.
| Function | Description |
|---|---|
scroll(ScrollState&) | Scroll on both axes, viewport = allocated size |
scroll(ScrollState&, int viewport_h) | Vertical scroll, fixed viewport height |
scroll(ScrollState&, int w, int h) | Both axes, fixed viewport w×h |
scrolly(ScrollState&, int h) | Vertical-only scroll, fixed height |
scrollx(ScrollState&, int w) | Horizontal-only scroll, fixed width |
Text-Wrap Tags
| Tag | Effect on a text(...) node |
|---|---|
clip | TextWrap::TruncateEnd — hard-truncate at the box edge |
nowrap | TextWrap::NoWrap — overflow past the edge, never wrap |
WrappedNode
template <Node Inner>
struct WrappedNode;
Created automatically when a runtime pipe is applied to any Node. Satisfies Node.
Multiple runtime pipes chain onto the same WrappedNode without extra nesting.
DSL Factory Functions
// Compile-time text
template <Str S>
inline constexpr TextNode<S> t;
// Vertical stack — accepts Nodes and ElementRanges (e.g. vector<Element>)
template <DslChild... Cs>
constexpr auto v(Cs... cs) -> BoxNode<Column, BoxCfg{}, Cs...>;
// Horizontal stack — accepts Nodes and ElementRanges (e.g. vector<Element>)
template <DslChild... Cs>
constexpr auto h(Cs... cs) -> BoxNode<Row, BoxCfg{}, Cs...>;
// Runtime builders (promoted from detail namespace)
auto box() -> BoxBuilder; // Base builder
auto vstack() -> BoxBuilder; // Column direction
auto hstack() -> BoxBuilder; // Row direction
auto center() -> BoxBuilder; // Centered, grow=1
// Z-stack — layer children on top of one another
Element zstack(std::vector<Element> layers);
// Measure-aware component: receives the (width, height) it was allotted
Element component(std::function<Element(int w, int h)> fn);
// Responsive layout toolkit (see "Responsive layout" below)
Size measure_element(const Element& el, int max_w, int max_h = 1 << 20);
auto fill(std::function<Element(int w, int h)> fn, int min_w = 0, int min_h = 1);
auto adapt(std::function<Element(int w)> fn);
auto fit_row(std::vector<FitItem> items, int gap = 0);
auto fit_col(std::vector<FitItem> items, int gap = 0);
auto pick(std::vector<Element> alternatives);
auto clamp(Element el, int max_width, HAlign align = HAlign::Center);
auto responsive(std::vector<Bp> tiers);
Element place(Element child, HAlign h = HAlign::Center, VAlign v = VAlign::Middle);
// The grid — responsive layout with one number (see "Responsive layout" below)
auto row(std::vector<Element> cells, int min_width = 24) -> ComponentBuilder;
Element col(std::vector<Element> cells, int gap = 0);
auto grid(std::vector<Element> cells, int min_width) -> ComponentBuilder;
auto sidebar(Element rail, Element main, int width) -> ComponentBuilder;
// Pretty text (see "Gradients" below)
Element gradient(std::string text, Color from, Color to, Style base = {});
Element gradient(std::string text, Gradient g, Style base = {});
Element rainbow(std::string text, Style base = {},
float saturation = 0.85f, float lightness = 0.62f);
auto gradient_rule(Color from, Color to, char32_t glyph = U'─');
auto gradient_rule(Gradient g, char32_t glyph = U'─');
// Empty element (renders nothing, satisfies Node)
Element nothing();
// Zero-copy references (no element copy): render an externally-owned
// element vector / sealed ScrollbackLedger in place
Element list_ref(const std::vector<Element>* items);
Element ledger_ref(const ScrollbackLedger& ledger);
// Runtime text
template <typename S>
auto text(S&& content, Style s = {}) -> RuntimeTextNode<decay_t<S>>;
template <typename S>
auto text(S&& content, Style s, TextWrap w) -> RuntimeTextNode<decay_t<S>>;
// Dynamic node
template <typename F>
auto dyn(F&& fn) -> DynNode<decay_t<F>>;
// Map range
template <std::ranges::range R, typename Proj>
auto map(R&& range, Proj&& proj) -> MapNode<decay_t<R>, decay_t<Proj>>;
// each() — alias for map(), for discoverability
template <std::ranges::range R, typename Proj>
auto each(R&& range, Proj&& proj);
// Spacer/separator/blank (function forms)
constexpr auto spacer() -> SpacerNode;
constexpr auto separator() -> SepNode;
constexpr auto blank() -> BlankNode;
DSL Constants
inline constexpr SpacerNode space;
inline constexpr SepNode sep;
inline constexpr VSepNode vsep;
inline constexpr BlankNode blank_;
inline constexpr BorderStyle Round = BorderStyle::Round;
inline constexpr BorderStyle Single = BorderStyle::Single;
inline constexpr BorderStyle Thick = BorderStyle::Bold;
inline constexpr BorderStyle Double = BorderStyle::Double;
Element Types
Element
struct Element {
std::variant<BoxElement, TextElement, ElementList> inner;
// Implicit constructors from each variant type
Element(BoxElement);
Element(TextElement);
Element(ElementList);
Element build() const; // Returns *this (satisfies Node concept)
};
TextElement
struct TextElement {
std::string content;
Style style{};
TextWrap wrap{TextWrap::Wrap};
Size measure(int max_width) const;
std::vector<std::string> format(int max_width) const;
};
BoxElement
struct BoxElement {
FlexStyle layout{};
Style style{};
BorderConfig border{};
Overflow overflow{Overflow::Visible};
std::vector<Element> children;
bool has_border() const;
int inner_horizontal() const; // padding + border horizontal
int inner_vertical() const; // padding + border vertical
};
ElementList
struct ElementList {
std::vector<Element> items;
};
TextWrap
enum class TextWrap {
Wrap, // Word wrap at container width
TruncateEnd, // "Long te..."
TruncateMiddle, // "Lon...xt"
TruncateStart, // "...g text"
NoWrap // Overflow
};
Style System
Style
struct Style {
std::optional<Color> fg, bg;
bool bold = false, dim = false, italic = false;
bool underline = false, strikethrough = false, inverse = false;
Style with_fg(Color c) const;
Style with_bg(Color c) const;
Style with_bold(bool v = true) const;
Style with_dim(bool v = true) const;
Style with_italic(bool v = true) const;
Style with_underline(bool v = true) const;
Style with_strikethrough(bool v = true) const;
Style with_inverse(bool v = true) const;
Style merge(const Style& other) const;
bool empty() const;
std::string to_sgr() const;
};
Style operator|(const Style& lhs, const Style& rhs); // Merge
Color
class Color {
public:
enum class Kind { Named, Indexed, Rgb };
// Named colors (constexpr)
static constexpr Color black();
static constexpr Color red();
static constexpr Color green();
static constexpr Color yellow();
static constexpr Color blue();
static constexpr Color magenta();
static constexpr Color cyan();
static constexpr Color white();
static constexpr Color bright_black(); // gray
static constexpr Color bright_red();
static constexpr Color bright_green();
static constexpr Color bright_yellow();
static constexpr Color bright_blue();
static constexpr Color bright_magenta();
static constexpr Color bright_cyan();
static constexpr Color bright_white();
static constexpr Color gray();
// RGB (constexpr)
static constexpr Color rgb(uint8_t r, uint8_t g, uint8_t b);
// Hex (consteval — compile-time only)
static consteval Color hex(uint32_t rgb);
// HSL (constexpr)
static constexpr Color hsl(float h, float s, float l);
// Indexed (256-color)
static Color indexed(uint8_t index);
// Accessors
Kind kind() const;
uint8_t r() const, g() const, b() const;
uint8_t index() const;
// Adjustment (constexpr)
Color lighten(float amount) const;
Color darken(float amount) const;
Color to_rgb() const; // resolve Named/Indexed → true RGB channels
// SGR sequences
std::string fg_sgr() const;
std::string bg_sgr() const;
};
Theme
struct Theme {
Color primary, secondary, accent;
Color success, error, warning, info;
Color text, inverse_text, muted;
Color surface, background, border;
Color diff_added, diff_removed, diff_changed;
Color highlight, selection, cursor, link;
Color placeholder, shadow, overlay;
static constexpr Theme derive(Theme base, auto&& patch);
};
namespace theme {
inline constexpr Theme dark;
inline constexpr Theme light;
inline constexpr Theme dark_ansi;
inline constexpr Theme light_ansi;
}
Border
BorderStyle
enum class BorderStyle {
None, Single, Double, Round, Bold,
SingleDouble, DoubleSingle, Classic, Arrow
};
BorderSides
struct BorderSides {
bool top = false, right = false, bottom = false, left = false;
static BorderSides all();
static BorderSides none();
static BorderSides horizontal(); // top + bottom
static BorderSides vertical(); // left + right
};
BorderText
struct BorderText {
std::string content;
BorderTextPos position = BorderTextPos::Top;
BorderTextAlign align = BorderTextAlign::Start;
int offset = 0;
};
BorderColors
struct BorderColors {
std::optional<Color> top, right, bottom, left;
static BorderColors uniform(Color c);
};
BorderConfig
struct BorderConfig {
BorderStyle style = BorderStyle::None;
BorderSides sides{};
BorderColors colors{};
std::optional<BorderText> text;
bool empty() const;
};
Layout Types
FlexDirection
enum class FlexDirection { Row, Column, RowReverse, ColumnReverse };
FlexWrap
enum class FlexWrap { NoWrap, Wrap, WrapReverse };
Align
enum class Align { Start, Center, End, Stretch, Baseline, Auto };
Justify
enum class Justify { Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly };
Overflow
enum class Overflow { Visible, Hidden, Scroll };
Dimension
struct Dimension {
enum class Kind { Auto, Fixed, Percent };
Kind kind;
float value;
static Dimension auto_();
static Dimension fixed(int v);
static Dimension percent(float v);
bool is_auto() const;
bool is_fixed() const;
bool is_percent() const;
int resolve(int parent) const;
};
Dimension operator""_pct(unsigned long long v); // 50_pct
FlexStyle
struct FlexStyle {
FlexDirection direction = FlexDirection::Row;
FlexWrap wrap = FlexWrap::NoWrap;
Align align_items = Align::Stretch;
Align align_self = Align::Auto;
Justify justify = Justify::Start;
float grow = 0;
float shrink = 1;
Dimension basis = Dimension::auto_();
Dimension width = Dimension::auto_();
Dimension height = Dimension::auto_();
Dimension min_width = Dimension::auto_();
Dimension min_height = Dimension::auto_();
Dimension max_width = Dimension::auto_();
Dimension max_height = Dimension::auto_();
int gap = 0;
Edges<int> padding{};
Edges<int> margin{};
};
Edges<T>
template <typename T>
struct Edges {
T top{}, right{}, bottom{}, left{};
Edges() = default;
Edges(T all); // uniform
Edges(T v, T h); // vertical, horizontal
Edges(T t, T r, T b, T l);
T horizontal() const; // left + right
T vertical() const; // top + bottom
};
Responsive Layout
Measure-driven primitives for layouts that restructure with the terminal size.
Full treatment in Responsive Layouts. All are in maya::dsl
(and maya::); solve_columns and its types live in
<maya/layout/columns.hpp>.
row() / col() / grid() / GridOpts
// Cells side by side, sharing the width equally and EXACTLY (largest-
// remainder split — no ragged right edge) — wrapping, then stacking, by
// itself as the slot narrows: 4-across → 2×2 → one column. `min_width` is
// the one number: how wide one cell needs to be to look right.
auto row(std::vector<Element> cells, int min_width = 24) -> ComponentBuilder;
// Cells stacked top to bottom, each stretched to the full width (flex
// cross-stretch — the GTK "fill"). Pipe `| grow(1)` onto the child that
// should take the leftover height. Compose: col({ row({a, b}), table }).
Element col(std::vector<Element> cells, int gap = 0);
struct GridOpts {
int min = 24; // a cell's comfortable minimum width (columns)
int max_cols = 0; // cap cells-per-row; 0 = as many as fit
int gap_x = 1; // blank columns between cells
int gap_y = 0; // blank rows between rows
bool grow_rows = false; // rows share surplus height (definite slot)
};
// row's engine with the knobs exposed. Re-solved from the REAL slot width
// per frame (adapt() underneath), so nested grids collapse independently.
// A short last row keeps the same cell width so columns line up.
auto grid(std::vector<Element> cells, GridOpts opts = {}) -> ComponentBuilder;
auto grid(std::vector<Element> cells, int min_width) -> ComponentBuilder;
// row({cpu, mem, net, disk}) // side by side; stacks by itself
// grid(cells, {.min = 24, .max_cols = 2}) // capped 2-across flow
sidebar() / SidebarOpts
struct SidebarOpts {
int width = 32; // the rail's fixed width (columns)
int stack_below = 0; // stack when slot < this; 0 = auto (2×width)
int gap = 1; // blank columns between rail and main
bool right = false; // rail on the right instead of the left
};
// Fixed-width rail beside a main pane that takes the rest; the pair stacks
// vertically (reading order preserved) when the slot is too narrow. The
// default threshold (2×width) keeps them side-by-side only while the main
// pane gets at least as much as the rail.
auto sidebar(Element rail, Element main, SidebarOpts opts = {}) -> ComponentBuilder;
auto sidebar(Element rail, Element main, int width) -> ComponentBuilder;
// sidebar(row({cpu, mem, net, disk}), proc_table, 42)
// // wide: 42-cell rail + table · medium: stats flow over the table
// // narrow: one column — a whole dashboard in two lines.
measure_element()
// Runs a real layout pass over `el` and returns its natural size within the
// given bounds. The measurement uses the SAME engine the renderer uses, so it
// can never disagree with the eventual paint. Cheap enough to call per frame.
Size measure_element(const Element& el, int max_width, int max_height = 1 << 20);
Pass a large max_width (e.g. 1 << 14) for the natural one-line width; pass a
real width to learn how many rows the fragment wraps to.
fill()
// A component that GROWS to fill the (w, h) its flex container allocates, then
// calls `fn` with the real allocated size at paint time. `min_w`/`min_h` set
// the measured minimum so grow has a finite basis. Sets grow(1) internally.
// The container must be DEFINITE on the fill axis for it to expand.
auto fill(std::function<Element(int w, int h)> fn, int min_w = 0, int min_h = 1)
-> ComponentBuilder;
adapt()
// A component that BUILDS a different tree depending on the width it is given.
// `fn` receives the real allocated width at paint time; natural height is
// auto-measured from what `fn` returns (measure runs the callback).
auto adapt(std::function<Element(int w)> fn) -> ComponentBuilder;
fit_row() / FitItem
struct FitItem {
Element el;
int keep = kKeepAlways; // importance; lower ranks drop first
};
// A row that DROPS items when they don't fit: lowest-`keep` first (ties drop
// the rightmost) until the remainder fits. Widths come from measure_element
// over the real fragments. `kKeepAlways` items never drop. `gap` spaces the
// KEPT items. Grow spacers (dsl::space) measure 0 and always survive.
auto fit_row(std::vector<FitItem> items, int gap = 0) -> ComponentBuilder;
// The vertical fit_row: DROPS items when the slot is too SHORT, lowest-`keep`
// first, until what remains fits the rows actually given. Heights come from
// measure_element at the real slot width (wrapping accounted for). Natural
// size is ALL items — shedding only happens when a definite-height slot
// hands it fewer rows (flex shrink, on by default, delivers the budget).
auto fit_col(std::vector<FitItem> items, int gap = 0) -> ComponentBuilder;
pick()
// SwiftUI's ViewThatFits: the FIRST alternative whose real measured width
// fits the slot renders; richest first, the LAST is the always-rendered
// fallback. No breakpoints — the decision measures the actual fragments.
auto pick(std::vector<Element> alternatives) -> ComponentBuilder;
clamp()
// libadwaita's AdwClamp: content uses the full slot up to max_width, then
// stops growing and aligns (Center by default; Left/Right for corner-
// anchored content like toasts) — a web page's container column.
// Transparent below max_width. The "too wide" half of responsive design.
auto clamp(Element el, int max_width,
HAlign align = HAlign::Center) -> ComponentBuilder;
responsive() / Bp
struct Bp {
int min_width = 0;
std::function<Element(int w)> build;
};
// Named width breakpoints over adapt(): the widest tier whose min_width the
// slot satisfies builds the view (the builder receives the real width). If
// the slot is narrower than every tier, the smallest tier is used.
auto responsive(std::vector<Bp> tiers) -> ComponentBuilder;
place() / HAlign / VAlign
enum class HAlign { Left, Center, Right };
enum class VAlign { Top, Middle, Bottom };
// Fill the slot the flex parent allocates and pin `child` at the given
// corner / edge / center. Tracks every resize with zero arithmetic.
Element place(Element child, HAlign h = HAlign::Center,
VAlign v = VAlign::Middle);
!!! note
ComponentBuilder (returned by component / fill / adapt / fit_row
/ responsive / gradient_rule) satisfies the Node concept — components
go directly in v() / h() and accept runtime pipes (| grow(1),
| width(40), | hit(id)).
solve_columns() / ColSpec / ColPlan
#include <maya/layout/columns.hpp>
inline constexpr int kKeepAlways = std::numeric_limits<int>::max();
struct ColSpec {
int min = 1; // minimum content width (cells)
int max = 0; // growth cap; 0 = unbounded (weight > 0 only)
float weight = 0.0f; // share of surplus space; 0 = fixed at min
int keep = kKeepAlways; // drop order: LOWER dropped first
};
struct ColPlan {
std::vector<int> width; // solved widths; 0 = dropped
int gap = 0;
bool has(std::size_t i) const; // is column i visible?
int at(std::size_t i) const; // solved width (0 when dropped/out of range)
int used() const; // total cells: visible widths + inter-column gaps
};
// Solve one shared width plan for a table, so the header row and every body row
// read the same column widths. Drop phase (lowest keep first) then weighted
// surplus waterfill clamped at max. With an unbounded weighted column the plan
// fills `avail` exactly. Pure arithmetic — no Element types, no layout engine.
ColPlan solve_columns(std::span<const ColSpec> cols, int avail, int gap = 1);
Gradients
Multi-color "pretty" primitives. Full treatment in Styling › Gradients.
Gradient
#include <maya/maya.hpp> // or <maya/style/gradient.hpp> standalone
struct Gradient {
std::vector<Color> stops;
Gradient(std::initializer_list<Color> stops);
static Gradient two(Color from, Color to);
// Sample at t ∈ [0,1] (clamped): linear RGB blend across the two nearest
// stops. Named/Indexed stops resolve via Color::to_rgb() first.
Color at(float t) const;
};
gradient() / rainbow() / gradient_rule()
// Horizontal gradient text — ONE TextElement with per-codepoint StyledRuns,
// so it wraps/truncates/measures exactly like plain text(). `base` carries
// non-color attributes (bold/italic) into every run.
Element gradient(std::string text, Color from, Color to, Style base = {});
Element gradient(std::string text, Gradient g, Style base = {});
// Full HSL hue sweep across the text width.
Element rainbow(std::string text, Style base = {},
float saturation = 0.85f, float lightness = 0.62f);
// Full-width divider that re-tiles `glyph` to its real allocated width and
// sweeps the gradient across it. Responsive by construction.
auto gradient_rule(Color from, Color to, char32_t glyph = U'─') -> ComponentBuilder;
auto gradient_rule(Gradient g, char32_t glyph = U'─') -> ComponentBuilder;
Event System
Event
using Event = std::variant<KeyEvent, MouseEvent, PasteEvent, FocusEvent, ResizeEvent>;
KeyEvent
using Key = std::variant<CharKey, SpecialKey>;
struct CharKey { char32_t codepoint; };
enum class SpecialKey {
Up, Down, Left, Right, Home, End,
PageUp, PageDown, Tab, BackTab,
Backspace, Delete, Insert, Enter, Escape,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12
};
struct Modifiers {
bool ctrl = false, alt = false, shift = false, super_ = false;
bool none() const;
};
struct KeyEvent {
Key key;
Modifiers mods;
std::string raw_sequence;
};
MouseEvent
enum class MouseButton { Left, Right, Middle, ScrollUp, ScrollDown, None };
enum class MouseEventKind { Press, Release, Move };
struct MouseEvent {
MouseButton button;
MouseEventKind kind;
Columns x;
Rows y;
Modifiers mods;
};
Other Events
struct PasteEvent { std::string content; };
struct FocusEvent { bool focused; };
struct ResizeEvent { Columns width; Rows height; };
Event Predicates
// Keyboard
bool key(const Event&, char c);
bool key(const Event&, char32_t cp);
bool key(const Event&, SpecialKey sk);
bool ctrl(const Event&, char c);
bool alt(const Event&, char c);
bool shift(const Event&, SpecialKey sk);
bool any_key(const Event&);
const KeyEvent* as_key(const Event&);
// Mouse
struct MousePos { int col, row; };
bool mouse_clicked(const Event&, MouseButton = MouseButton::Left);
bool mouse_released(const Event&, MouseButton = MouseButton::Left);
bool mouse_moved(const Event&);
bool scrolled_up(const Event&);
bool scrolled_down(const Event&);
std::optional<MousePos> mouse_pos(const Event&);
const MouseEvent* as_mouse(const Event&);
// Other
bool resized(const Event&, int* w = nullptr, int* h = nullptr);
bool pasted(const Event&, std::string* out = nullptr);
bool focused(const Event&);
bool unfocused(const Event&);
// Fire-and-forget
template <typename F> bool on(const Event&, char c, F&& fn);
template <typename F> bool on(const Event&, char c1, char c2, F&& fn);
template <typename F> bool on(const Event&, SpecialKey sk, F&& fn);
Signals
Signal<T>
template <typename T>
class Signal {
Signal(); // Default-construct T
explicit Signal(U&& initial); // Construct from value
const T& get() const; // Read (auto-tracks dependencies)
const T& operator()() const; // Shorthand for get()
void set(const T& v); // Write (notifies if changed)
void set(T&& v); // Write (move)
void update(F&& fn); // Mutate in-place (always notifies)
auto map(F&& fn) const -> Computed<R>; // Derive computed value
uint64_t version() const; // Change counter
};
Computed<T>
template <typename T>
class Computed {
const T& get() const; // Read (recomputes if dirty)
const T& operator()() const;
};
template <std::invocable F>
auto computed(F&& fn) -> Computed<invoke_result_t<F>>;
Effect
class Effect {
Effect(); // Default (inactive)
explicit Effect(F&& fn); // Create and run immediately
void dispose(); // Unsubscribe from all deps
bool active() const;
};
template <std::invocable F>
Effect effect(F&& fn);
Batch
class Batch {
Batch(); // Begin batch
~Batch(); // End batch, flush notifications
};
template <std::invocable F>
decltype(auto) batch(F&& fn); // Run fn inside a batch scope
Canvas
Canvas
class Canvas {
Canvas(int width, int height, StylePool* pool);
void set(int x, int y, char32_t ch, uint16_t style_id, uint8_t width = 0);
void write_text(int x, int y, std::string_view text, uint16_t style_id);
void fill(Rect region, char32_t ch, uint16_t style_id);
void clear();
void resize(int w, int h);
Cell get(int x, int y) const;
int width() const;
int height() const;
void push_clip(Rect clip);
void pop_clip();
};
Cell
struct Cell {
char32_t character;
uint16_t style_id;
uint16_t hyperlink_id;
uint8_t width;
uint64_t pack() const;
static Cell unpack(uint64_t v);
};
StylePool
class StylePool {
uint16_t intern(const Style& s);
const Style& get(uint16_t id) const;
void clear();
std::size_t size() const;
};
render_tree()
void render_tree(const Element& root, Canvas& canvas,
StylePool& pool, const Theme& theme);
Widgets
All widgets live in maya::widget. Full documentation in 13-widgets.md.
Input
| Widget | Header | Description |
|---|---|---|
Input | widget/input.hpp | Single-line text input with cursor and history |
TextArea | widget/textarea.hpp | Multi-line text editor |
Checkbox | widget/checkbox.hpp | Toggle checkbox |
ToggleSwitch | widget/checkbox.hpp | iOS-style toggle switch |
Radio | widget/radio.hpp | Radio button group |
Select | widget/select.hpp | Dropdown select menu |
Slider | widget/slider.hpp | Numeric slider |
Button | widget/button.hpp | Clickable button with variants |
CommandPalette | widget/command_palette.hpp | Fuzzy-search command launcher; clamped, rows shed detail when narrow |
Data Display
| Widget | Header | Description |
|---|---|---|
Table | widget/table.hpp | Data table: rich cells (spans + width-adaptive builders), selection, height-aware windowing + scrollbar, host-owned scroll, sort indicators, flexible + shedding columns, hit rects, flow_rows() per-row Elements for host-owned flows |
Tree | widget/tree.hpp | Expandable tree view |
List | widget/list.hpp | Selectable item list |
KeyHelp | widget/key_help.hpp | Keyboard shortcut legend; 2-col ↔ 1-col by real measurement (pick) |
Badge | widget/badge.hpp | Inline status badge |
Callout | widget/callout.hpp | Info/success/warning/error callout box |
Link | widget/link.hpp | Hyperlink element |
ShortcutRow | widget/shortcut_row.hpp | Width-adaptive keyboard hint row (Helix/k9s style) |
ModelBadge | widget/model_badge.hpp | Color-coded active-model indicator |
Navigation
| Widget | Header | Description |
|---|---|---|
Tabs | widget/tabs.hpp | Tab bar navigation; falls back to ‹ active i/n › when narrow (pick) |
Breadcrumb | widget/breadcrumb.hpp | Breadcrumb path navigation; collapses to first › … › last (pick) |
Menu | widget/menu.hpp | Menu with selectable items; shortcuts shed when narrow |
ActivityBar | widget/activity_bar.hpp | Vertical icon sidebar |
Scrollable | widget/scrollable.hpp | Scrollable content region |
Scrollbar | widget/scrollbar.hpp | Visual indicator for a ScrollState |
Picker | widget/picker.hpp | Bordered modal picker with scrollable results |
CommandPalette | widget/command_palette.hpp | Fuzzy-search command launcher; clamped, rows shed detail when narrow |
Display
| Widget | Header | Description |
|---|---|---|
StreamingMarkdown | widget/markdown.hpp | Streaming markdown renderer |
Spinner | widget/spinner.hpp | Animated loading spinner |
ProgressBar | widget/progress.hpp | Progress bar with percentage |
Gauge | widget/gauge.hpp | Gauge / meter display |
Divider | widget/divider.hpp | Horizontal or vertical divider |
Image | widget/image.hpp | Terminal image display |
gradient() | widget/gradient.hpp | Color gradient text builder |
Disclosure | widget/disclosure.hpp | Collapsible disclosure section |
Html | widget/html.hpp | Render a safe subset of HTML as styled Elements |
Overlay | widget/overlay.hpp | Base layer + one anchored floating element |
Overlay
| Widget | Header | Description |
|---|---|---|
Modal | widget/modal.hpp | Modal dialog with buttons; clamped to dialog width |
Popup | widget/popup.hpp | Floating popup |
ToastManager | widget/toast.hpp | Toast notification manager; clamped right-anchored cards |
Visualization
| Widget | Header | Description |
|---|---|---|
BarChart | widget/bar_chart.hpp | Horizontal/vertical bar chart |
LineChart | widget/line_chart.hpp | Line chart with series |
Sparkline | widget/sparkline.hpp | Inline sparkline graph |
Heatmap | widget/heatmap.hpp | Grid heatmap |
Calendar | widget/calendar.hpp | Calendar date display |
PixelCanvas | widget/canvas.hpp | Pixel-level drawing canvas |
FlameChart | widget/flame_chart.hpp | Flame graph for nested execution spans |
Waterfall | widget/waterfall.hpp | Timing waterfall chart (devtools style) |
Timeline | widget/timeline.hpp | Vertical CI/pipeline event timeline |
GitGraph | widget/git_graph.hpp | Commit graph with colored branch lines |
Agent UI
Composable pieces of a Claude-Code / Zed-style agent conversation. Thread
is the top-level viewport; the rest are the parts it (or a host) assembles.
| Widget | Header | Description |
|---|---|---|
Thread | widget/thread.hpp | Top-level viewport: empty → WelcomeScreen, else Conversation |
WelcomeScreen | widget/welcome_screen.hpp | Empty-thread brand splash + starters/hints |
Conversation | widget/conversation.hpp | Vertical list of turns with dividers + in-flight indicator |
Turn | widget/turn.hpp | One speaker turn (rail, role, checkpoint) |
TurnDivider | widget/turn_divider.hpp | Styled rule between conversation turns |
CheckpointDivider | widget/checkpoint_divider.hpp | Full-width "↺ Restore checkpoint" rule above a turn |
Composer | widget/composer.hpp | State-driven bordered input box (idle/streaming/permission) |
AgentTimeline | widget/agent_timeline.hpp | Bordered Actions panel logging tool events for a turn |
ToolBodyPreview | widget/tool_body_preview.hpp | Per-ToolKind body detail under a timeline event |
AppLayout | widget/app_layout.hpp | Top-level chat-app frame (header/body/composer/overlay) |
UserMessage | widget/message.hpp | Chat user message bubble |
AssistantMessage | widget/message.hpp | Chat assistant message bubble |
ThinkingBlock | widget/thinking.hpp | Collapsible AI thinking block |
StreamingCursor | widget/streaming_cursor.hpp | Pulsing "assistant is streaming" indicator |
SystemBanner | widget/system_banner.hpp | Severity-coloured system alert (ctx warning, rate limit) |
TodoList | widget/todo_list.hpp | Session todo list card |
PlanView | widget/plan_view.hpp | Task plan with status tracking |
Permission | widget/permission.hpp | Permission approval prompt |
Session / Diagnostics
| Widget | Header | Description |
|---|---|---|
DiffView | widget/diff_view.hpp | Side-by-side or unified diff |
InlineDiff | widget/inline_diff.hpp | Two-line word-level LCS diff |
FileChanges | widget/file_changes.hpp | Session file-change summary (created/modified/deleted + counts) |
ChangesStrip | widget/changes_strip.hpp | Bordered "session has pending changes" banner |
FileRef | widget/file_ref.hpp | File reference with icon; dir collapses to …/ when narrow |
LogViewer | widget/log_viewer.hpp | Filterable log viewer |
SearchResult | widget/search_result.hpp | Grouped search results; paths keep their filename when narrow |
ErrorBlock | widget/error_block.hpp | Structured error/exception card |
GitStatus | widget/git_status.hpp | Branch + working-tree status |
ContextWindow | widget/context_window.hpp | Segmented context-window usage meter |
TokenStream | widget/token_stream.hpp | Live token-rate sparkline + stats (compact/full) |
CostTracker | widget/cost_tracker.hpp | Per-turn + cumulative token/cost breakdown |
ApiUsage | widget/api_usage.hpp | API rate-limit / request-count / latency display |
ActivityIndicator | widget/activity_indicator.hpp | Single-row hex-dump activity tape |
Status Bar
| Widget | Header | Description |
|---|---|---|
StatusBar | widget/status_bar.hpp | Single-line streaming status bar (composes the pieces below) |
TokenStreamSparkline | widget/token_stream_sparkline.hpp | Live tok/s rate + sparkline chip (adaptive width) |
PhaseChip | widget/phase_chip.hpp | Current-phase verb + elapsed, breathing |
TitleChip | widget/title_chip.hpp | Breadcrumb / title chip |
ContextGauge | widget/context_gauge.hpp | Context-window usage gauge |
PhaseAccent | widget/phase_accent.hpp | Top/bottom accent rail strip |
StatusBanner | widget/status_banner.hpp | Full-width toast that takes over the activity row |
Tool Widgets
| Widget | Header | Description |
|---|---|---|
ToolCall | widget/tool_call.hpp | Generic tool invocation display |
BashTool | widget/bash_tool.hpp | Shell command tool call |
ReadTool | widget/read_tool.hpp | File read tool call |
EditTool | widget/edit_tool.hpp | File edit tool call |
WriteTool | widget/write_tool.hpp | File write tool call |
FetchTool | widget/fetch_tool.hpp | HTTP fetch tool call |
AgentTool | widget/agent_tool.hpp | Sub-agent tool call |
GitCommitTool | widget/git_commit_tool.hpp | Git commit operation card |
Core Types
Strong<Tag, T>
template <typename Tag, typename T>
struct Strong {
T value{};
// Arithmetic: Strong + Strong, Strong - Strong, etc.
// Comparison: ==, !=, <, >, <=, >=
};
Size / Position / Rect
using Columns = Strong<ColumnTag, int>;
using Rows = Strong<RowTag, int>;
struct Size { Columns width; Rows height; };
struct Position { Columns x; Rows y; };
struct Rect { Position pos; Size size; };
Result / Status / Error
enum class ErrorKind {
TerminalInit, Io, LayoutOverflow, InvalidStyle,
InvalidUtf8, Unsupported, Signal, WouldBlock
};
struct Error {
ErrorKind kind;
std::string message;
std::source_location location;
static Error terminal(std::string msg);
static Error io(std::string msg);
static Error from_errno(std::string ctx);
};
template <typename T>
using Result = std::expected<T, Error>;
using Status = Result<void>;