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, reactive signals, and high-performance SIMD-accelerated rendering to produce beautiful, fast TUIs with minimal ceremony.
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.
Zero ceremony. A full interactive TUI is one function call:
#include <maya/maya.hpp>
using namespace maya;
using namespace maya::dsl;
int main() {
Signal<int> n{0};
run(
[&](const Event& ev) {
on(ev, '+', [&] { n.update([](int& x) { ++x; }); });
return !key(ev, 'q');
},
[&] {
return (v(
dyn([&] { return text("Count: " + std::to_string(n.get())); }),
t<"[+] increment [q] quit"> | Dim
) | pad<1>).build();
}
);
}
For larger apps, the Program architecture gives you pure functions, testable logic, and effects as data:
struct Counter {
struct Model { int n = 0; };
struct Inc {}; struct Quit {};
using Msg = std::variant<Inc, Quit>;
static Model init() { return {}; }
static auto update(Model m, Msg msg) -> std::pair<Model, Cmd<Msg>> {
return std::visit(overload{
[&](Inc) { return std::pair{Model{m.n + 1}, Cmd<Msg>{}}; },
[](Quit) { return std::pair{Model{}, Cmd<Msg>::quit()}; },
}, msg);
}
static Element view(const Model& m) {
return (v(text("Count: " + std::to_string(m.n)) | Bold,
t<"[+] increment [q] quit"> | Dim) | pad<1>).build();
}
static auto subscribe(const Model&) -> Sub<Msg> {
return key_map<Msg>({{'+', Inc{}}, {'q', Quit{}}});
}
};
int main() { run<Counter>({.title = "counter"}); }
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.
Runtime polymorphism happens through std::visit with the overload{...}
pattern.
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 |
| Reactive signals | Signal<T>, Computed<T>, Effect, Batch — SolidJS-style fine-grained reactivity |
| Four rendering modes | run<P>() (fullscreen/inline), live() (animated output), canvas_run() (imperative), print() (one-shot) |
| Rich input | Keys, mouse clicks/movement/scroll, paste, focus, resize — all via pattern matching |
| Themes | 24-slot color themes with dark/light built-ins and compile-time derivation |
| Canvas API | Direct cell-level painting for games, visualizations, and animations |
| 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 │
│ using namespace maya::dsl; │
│ auto ui = v(t<"Hello">, ...) | border_<Round>; │
├─────────────────────────────────────────────────────┤
│ DSL Layer (dsl.hpp) │
│ TextNode, BoxNode, DynNode, MapNode, RuntimeTextNode│
│ Compile-time type-state validation │
├─────────────────────────────────────────────────────┤
│ Element Layer (element/) │
│ Element = variant<BoxElement, TextElement, ElementList>│
│ BoxBuilder for runtime construction │
├─────────────────────────────────────────────────────┤
│ Layout Engine (layout/) │
│ Pure C++ flexbox solver — no dependencies │
│ Direction, wrapping, alignment, sizing constraints │
├─────────────────────────────────────────────────────┤
│ Render Pipeline (render/) │
│ Canvas → StylePool → SIMD Diff → ANSI Serialize │
├─────────────────────────────────────────────────────┤
│ Terminal Layer (terminal/) │
│ Raw mode, alt screen, input parsing, ANSI output │
│ Type-state: Cooked → Raw → AltScreen │
├─────────────────────────────────────────────────────┤
│ App Framework (app/) │
│ run<P>(), Program, Cmd<Msg>, Sub<Msg>, │
│ live(), canvas_run(), print() │
└─────────────────────────────────────────────────────┘
Quick Taste¶
Static UI (compile-time)¶
constexpr 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.build());
Interactive app (fullscreen)¶
Signal<int> count{0};
run(
{.title = "counter", .fps = 30},
[&](const Event& ev) {
on(ev, '+', '=', [&] { count.update([](int& n) { ++n; }); });
return !key(ev, 'q');
},
[&](const Ctx& ctx) {
return (v(
dyn([&] { return text("Count: " + std::to_string(count.get()),
Style{}.with_bold().with_fg(ctx.theme.primary)); }),
t<"[+] count [q] quit"> | Dim
) | border_<Round> | pad<1>).build();
}
);
Live progress (no alt screen)¶
int n = 0;
live({.fps = 30}, [&](float dt) {
if (++n > 200) quit();
return (v(
text("Installing... " + std::to_string(n) + "/200") | Bold,
text(std::string(n / 10, '#') + std::string(20 - n / 10, '.'))
) | pad<0, 1>).build();
});
Canvas animation (imperative)¶
canvas_run(
{.fps = 60, .mouse = true},
[&](StylePool& pool, int w, int h) { /* intern styles */ },
[&](const Event& ev) { return !key(ev, 'q'); },
[&](Canvas& canvas, int w, int h) {
canvas.set(w/2, h/2, U'*', style_id);
}
);
Documentation Map¶
| Document | What You'll Learn |
|---|---|
| Getting Started | Installation, CMake setup, building your first app |
| 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 | dyn(), text(), map(), mixing static and dynamic |
| Event Handling | Keys, mouse, resize, on() helpers, event predicates |
| Rendering Modes | run<P>(), live(), canvas_run(), print() |
| Canvas API | Low-level painting, StylePool, cells, animations |
| Signals & Reactivity | Signal<T>, Computed<T>, Effect, Batch |
| Examples Walkthrough | Annotated guide through all 10 built-in examples |
| API Reference | Complete type and function reference |