maya — A C++26 Type-Safe TUI Library¶
maya is a compile-time, type-safe terminal UI library for C++26. It combines a declarative DSL with a flexbox layout engine and high-performance SIMD-accelerated rendering to produce beautiful, fast TUIs with minimal ceremony.
maya is to jaal what Ink is to React: jaal runs the
program — model, messages, update, effects (Cmd) and subscriptions
(Sub) — and maya draws it. There is one way to run an app, and it is the
same for a counter, a dashboard, a fractal zoomer, or an inline progress bar.
Philosophy¶
Impossible states don't compile. maya's DSL uses C++26 template metaprogramming and type-state machines to catch errors at compile time. You can't set a border color without first declaring a border style — the compiler rejects it. Padding values can't be negative. Style modifiers only compose where they make sense.
One program shape. A maya app is a struct with a Model, a Msg variant,
one update overload per message, a pure view, and a subscribe that says
which events and timers it listens to. run<P>() does the rest:
#include <maya/host/run.hpp>
using namespace maya;
using namespace maya::dsl;
struct Counter {
struct Model { int count = 0; };
struct Inc {}; struct Dec {}; struct Quit {};
using Msg = std::variant<Inc, Dec, Quit>;
using Cmd = jaal::Cmd<Msg>;
static Cmd update(Model& m, Inc) { ++m.count; return {}; }
static Cmd update(Model& m, Dec) { --m.count; return {}; }
static Cmd update(Model&, Quit) { return Cmd::quit(0); }
static Element view(const Model& m) {
return v(
text("Count: " + std::to_string(m.count)) | Bold | Fg<100, 200, 255>,
t<"[+/-] change [q] quit"> | Dim
) | border_<Round> | bcol<50, 55, 70> | pad<1>;
}
static std::optional<Msg> on(const Model&, const KeyEvent& k) {
static const keymap<Msg> keys{{'+', Inc{}}, {'-', Dec{}}, {'q', Quit{}}};
return keys(k);
}
};
int main() { return run<Counter>({.title = "counter"}); }
Everything the model says, the view says. No globals, no hidden widget
state, no callbacks writing into captured variables. view() is a pure
function of the model, so it is trivially testable. Animation is a
Sub::every in subscribe() — which is also where it stops, so a still
screen costs nothing.
Say what you use. The rows of Cmd list the terminal effects a program
needs (jaal::Cmd<Msg, set_title>); asking for something the host can't
provide is a compile error, not a runtime surprise. Input is one
on(const Model&, const KeyEvent&) overload per kind you care about
(MouseEvent, ResizeEvent, …); kinds with no overload are dropped.
Performance by default. Styles are interned into 16-bit IDs. Cells are packed into 64-bit values. Frame diffing uses SIMD (AVX-512, AVX2, SSE2, NEON) to compare thousands of cells in microseconds. The layout engine is a single-pass flexbox solver with no heap allocation in the hot path.
Composition over inheritance. No virtual classes, no CRTP hierarchies. UI
trees are plain data — std::variant of TextElement, BoxElement, and
ElementList. The DSL layer is pure templates that produce these data types.
Coming from the old API? The callback loops (
run(event_fn, render_fn),live(),canvas_run()),maya::Cmd/maya::Sub,key_map, andRunConfigare gone. Everything is now a program run byrun<P>(Options); a canvas demo is a program whose view ispixels(img), and a live progress line is a program run with.mode = Mode::Inline.
What maya Gives You¶
| Feature | Description |
|---|---|
| Compile-time DSL | t<"Hello"> \| Bold \| Fg<100,180,255> — text, style, layout as template parameters |
| Flexbox layout | Row/column stacking, padding, margin, grow/shrink, gap, alignment, wrapping |
| Borders | Single, Double, Round, Bold, Classic, Arrow — with colors, titles, per-side control |
| One program shape | Model + Msg + update + view + subscribe, run by run<P>() on jaal |
| Two modes | Mode::Fullscreen (alternate screen) or Mode::Inline (lives in scrollback); plus print() for one-shot output |
| Rich input | Keys, mouse clicks/movement/scroll, paste, focus, resize — one on(const Model&, Event) overload per kind |
| Terminal effects | Title, clipboard, scrollback commits, mouse toggling, suspend-for-$EDITOR — as Cmd rows |
| Pixel graphics | Image + pixels() for half-block RGB art, Glyphs + glyphs() for coloured character art |
| Themes | 24-slot color themes with dark/light built-ins and compile-time derivation |
| Unicode | Full UTF-8 support with CJK wide-character handling and braille sub-cell graphics |
| SIMD diffing | O(N/8) frame comparison for minimal terminal writes |
Architecture Overview¶
┌─────────────────────────────────────────────────────┐
│ User Code │
│ struct App { Model, Msg, update, view, subscribe } │
│ int main() { return run<App>({...}); } │
├─────────────────────────────────────────────────────┤
│ jaal (the runtime — third_party/jaal) │
│ The loop: messages, Cmd effects, Sub diffing, │
│ timers, worker threads, shutdown │
├─────────────────────────────────────────────────────┤
│ maya::app (app.hpp) │
│ run<P>(Options): opens the terminal, hands it to │
│ jaal. Input events (KeyEvent, MouseEvent, …) and │
│ terminal effects (set_title, suspend, …) │
├─────────────────────────────────────────────────────┤
│ DSL Layer (dsl.hpp) │
│ t<>, text(), v(), h(), pipes; type-state validation │
├─────────────────────────────────────────────────────┤
│ Element Layer (element/) │
│ Element = variant<BoxElement, TextElement, ElementList>│
│ pixels(Image), glyphs(Glyphs), widgets │
├─────────────────────────────────────────────────────┤
│ Layout Engine (layout/) │
│ Pure C++ flexbox solver — no dependencies │
├─────────────────────────────────────────────────────┤
│ Render Pipeline (render/) │
│ Canvas → StylePool → SIMD Diff → ANSI Serialize │
├─────────────────────────────────────────────────────┤
│ Terminal Layer (terminal/) │
│ Raw mode, alt screen, input parsing, ANSI output │
└─────────────────────────────────────────────────────┘
Quick Taste¶
Static output (no runtime)¶
#include <maya/maya.hpp>
using namespace maya;
using namespace maya::dsl;
int main() {
auto ui = v(
t<"System Status"> | Bold | Fg<220, 220, 240>,
h(t<"CPU:"> | Dim, t<" 42%"> | Bold | Fg<80, 220, 120>),
h(t<"Mem:"> | Dim, t<" 8.2G"> | Bold | Fg<180, 130, 255>)
) | border_<Round> | bcol<60, 65, 80> | pad<1>;
print(ui);
}
A timer (animation is a subscription)¶
using namespace std::chrono_literals;
struct Clock {
struct Model { int ticks = 0; bool paused = false; };
struct Tick {}; struct Pause {}; struct Quit {};
using Msg = std::variant<Tick, Pause, Quit>;
using Cmd = jaal::Cmd<Msg>;
using Sub = jaal::Sub<Msg>;
static Cmd update(Model& m, Tick) { ++m.ticks; return {}; }
static Cmd update(Model& m, Pause) { m.paused = !m.paused; return {}; }
static Cmd update(Model&, Quit) { return Cmd::quit(0); }
static Element view(const Model& m) {
return text("Ticks: " + std::to_string(m.ticks)) | Bold;
}
static std::optional<Msg> on(const Model&, const KeyEvent& k) {
static const keymap<Msg> keys{{' ', Pause{}}, {'q', Quit{}}};
return keys(k);
}
static Sub subscribe(const Model& m) {
if (m.paused) return Sub::none(); // no timer: nothing runs
return Sub::every(100ms, Tick{});
}
static bool subs_key(const Model& m) { return m.paused; }
};
Inline progress (no alt screen)¶
The same shape, run with run<Progress>({.mode = Mode::Inline}): the view
renders into the normal scrollback, and the final frame stays on screen when
the program quits. See examples/inline_progress.cpp.
Pixel graphics¶
static Element view(const Model& m) {
Image img(m.w, m.h); // h = 2 × rows (half blocks)
img.fill_rows([&](int x, int y) -> Rgb {
return {static_cast<std::uint8_t>(x * 4), static_cast<std::uint8_t>(y * 4), 128};
});
return pixels(std::move(img));
}
Documentation Map¶
| Document | What You'll Learn |
|---|---|
| Getting Started | Building, CMake setup, your first program, timers, input, pixels, inline mode |
| The DSL | Compile-time nodes, t<>, v(), h(), pipe operators |
| Styling | Colors, text attributes, themes, compile-time style tags |
| Layout | Flexbox model, direction, padding, grow, borders, alignment |
| Runtime Content | text(), map(), mixing static and dynamic content |
| Event Handling | on() overloads per input kind, keymap<Msg>, key predicates |
| Rendering Modes | run<P>(), Mode::Fullscreen vs Mode::Inline, print() |
| Canvas API | Low-level painting, StylePool, cells, Image/pixels() |
| Signals & Reactivity | Signal<T>, Computed<T>, Effect, Batch |
| Examples Walkthrough | Annotated guide through the built-in examples |
| API Reference | Complete type and function reference |