A terminal UI toolkit for MoonBit: cell buffer, differential ANSI renderer, incremental key parser, layout, a CSS-like design system, thirty-two widgets, and drivers for terminals, Node, browsers and serial lines.
| Synchronous core | No async and no hidden event loop. The host calls one step(): poll, parse, dispatch, lay out, compose, diff, write. |
| Differential renderer | Two frames become the spans that changed, and only the escape sequences needed to draw them. |
| A CSS-like design system | Selectors over name, id, class and :focus, :hover, :disabled, stacked by specificity and cascade, with inheritance, borders, padding, margins, docks, grid spans and transitions. |
| Unicode-aware | Every line is measured in terminal columns over a generated width table: wide CJK, double-width emoji, combining marks. |
| Four backends | The same code and the same test suite on wasm, wasm-gc, js and native. |
| Any wire | A modern terminal with true colour, a serial-era terminal with eight colours, a browser canvas, or a bare UART with no cursor addressing at all. |
moon add moonbitstack/moonetuimoon run --target native src/examples/gallery # every widget and every style, live
moon run --target native src/examples/monitor # a complete app: tabs, timers, a command line
moon run --target native src/examples/tui # a styled app in a real terminal
moon run --target js src/examples/hello # under Node
moon run --target native src/examples/uart # whole frames of plain text, as a serial line gets themfn main raise {
let sheet = @design.Stylesheet::new()
sheet.add(
@design.Sel::new(name="Header"),
@design.Styles::new(text=@style.Style::new(bold=true, fg=@style.cyan)),
)
sheet.add(@design.Sel::new(classes=["framed"]), @design.Styles::new(border=Round))
sheet.add(
@design.Sel::new(classes=["framed"], focus=true),
@design.Styles::new(border=Heavy),
)
let list = @widget.ListView::new(["alpha", "beta", "gamma"])
let field = @widget.Input::new(placeholder="pick one")
let line = @layout.Box::new(extent=Cells(1))
let tree = @app.column(0, [
@app.leaf(1, @widget.Header::new("moonetui"), box=line, name="Header"),
@app.leaf(2, list, classes=["framed"]),
@app.leaf(3, field, box=line),
@app.leaf(4, @widget.Footer::new([("tab", "next"), ("enter", "choose")]), box=line),
])
let ui = @app.App::new(@native.NativeTty::new(), tree, sheet~)
ui.start()
@native.run(() => {
let alive = ui.step()
for report in ui.drain() {
if report is Selected(index~, ..) {
field.set(list.items()[index])
ui.refresh()
}
}
alive
})
ui.stop()
}| Group | Widgets |
|---|---|
| Text and display | Static and label(), Header, Footer, Rule, Digits, Sparkline, Progress, LoadingIndicator, Placeholder |
| Entry and logs | Input, TextArea, Log, RichLog |
| Choices | Switch, Checkbox, RadioButton, RadioSet, OptionList, SelectionList, Select |
| Navigation and structure | Button, ListView, Tabs, TabbedContent, Collapsible, Tree, DirectoryTree |
| Data and notices | DataTable, Markdown, Tooltip, Toast |
| Package | What it does |
|---|---|
| @geom | Integer rectangle algebra: Offset, Size, Region, Spacing |
| @style | Colours (named, indexed, true colour), attributes, palette quantisation, differential SGR |
| @strip | One rendered line: styled segments measured in terminal columns |
| @event | Keys, mouse reports, paste, resize and focus as plain values |
| @input | The terminal byte stream turned into events |
| @comp | The compositor: placements to rows, rows to changed spans, spans to escape sequences |
| @driver | The seam to a terminal, and a headless terminal for tests |
| @vt | A virtual terminal: the screen an escape stream draws, for the browser driver, tests and screenshots |
| @layout | Sizes along an axis, and the grid with spans and gutters |
| @design | Rules over name, id, class and state; borders, padding, margins, alignment, docks, offsets, transitions |
| @widget | The widget contract and the inventory |
| @app | The widget tree, focus ring, scroll containers, docks, grids, screens, timers and the frame loop |
| @driver/native · @driver/node · @driver/web | A real terminal through the C library; Node; a canvas in the browser |
moon check --target all --deny-warn
moon build --target all
moon test --target all
moon fmt && moon info --target all # the diff must stay cleanInstall
Download zipA terminal UI toolkit for MoonBit: cell buffer, differential ANSI renderer, incremental key parser, layout, a CSS-like design system, thirty-two widgets, and drivers for terminals, Node, browsers and serial lines.