- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks are pending
Rust CI / verify (push) Waiting to run
Added: regression tests, Ratatui experiments, and migration findings. Changed: renderer, event loop, samples, generator, documentation, and CI for Ratatui 0.30.2. Removed: tui-rs dependency and obsolete polling thread. |
||
| .github/workflows | ||
| .sandbox/ratatui-experiments | ||
| .vscode | ||
| samples | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| DISCOVERY.md | ||
| LICENSE | ||
| README.md | ||
tui-markup-renderer
Build terminal interfaces from XML-like markup with Ratatui. This fork migrates the original tui-rs renderer to Ratatui 0.30.2 while keeping layouts, styled text, buttons, tabs, dialogs, actions, state, and custom renderers.
Minimum supported Rust version: 1.88.
Install and generate a project
Add this repository as a path dependency while working on the fork:
[dependencies]
tui-markup-renderer = { path = "/path/to/tui-markup-renderer" }
ratatui = "=0.30.2"
crossterm = "0.29"
Run the bundled generator from this checkout:
cargo run --bin tui-markup-gen -- -n demo -o /tmp -t simple
cargo run --bin tui-markup-gen -- -n dashboard -o /tmp -t shell
Generated manifests use a path dependency on the checkout that built the generator, so generated projects compile before this fork is published. Change that path when moving the project to another machine. Both generated variants run cargo check before reporting success.
Markup and application
Save as assets/main.xml:
<layout id="root" direction="vertical">
<styles>
p {fg:green}
button:focus {fg:white;bg:blue}
</styles>
<block border="all" title="Example" constraint="5">
<p align="center">Press Tab and Enter</p>
</block>
<button id="quit" index="1" action="quit" constraint="3">Quit</button>
</layout>
Use a Ratatui backend and pass it to ui_loop:
use std::{collections::HashMap, io};
use ratatui::backend::CrosstermBackend;
use tui_markup_renderer::{event_response::EventResponse, markup_parser::MarkupParser};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let backend = CrosstermBackend::new(io::stdout());
let mut ui = MarkupParser::try_new("assets/main.xml".into(), None, Some(HashMap::new()))?;
ui.add_action("quit", |_, _| EventResponse::QUIT);
ui.ui_loop(backend, |key, _state| {
if key.code == crossterm::event::KeyCode::Char('q') {
EventResponse::QUIT
} else {
EventResponse::NOOP
}
})
}
The loop handles Tab, Shift+Tab, and Enter for indexed controls, calls the application event callback for key presses, redraws on input or resize, and restores raw mode and the alternate screen when it exits or returns an error. EventResponse::STATE replaces the state map; CLEANFOCUS also clears focus. Actions receive the state map and the selected markup element.
Supported markup
layoutsplits children vertically or horizontally (direction).blockandcontainerprovide borders, titles, and nested composition.prenders wrapped, aligned text.buttonrenders a focusable control. Give interactive elements anindex,id, andaction.tabs,tabs-header,tab-item,tabs-body, andtab-content for="item-id"display one content panel at a time. The selected item is stored under<tabs-id>:index.dialog show="state-key" buttons="Yes|No"is visible when the state value istrue. Dialog button actions default toon_<dialog-id>_btn_<button-label>;actionoverrides that for all buttons.constraintaccepts a cell count (5), percentage (30%), minimum (10min), maximum (5max), or ratio (1:2). Like the original renderer, spare space in a split goes to the last child; Ratatui'sFlex::Legacyis selected explicitly for this markup behavior.borderacceptsall,none, ortop|bottom|left|right.styles,focus_styles, andactive_stylesacceptfg,bg,weight, andfont-decoration. Global rules inside<styles>can target an element name,name:focus,name:active, or#id.
A custom element renderer can be registered through RendererStorage::add_renderer. Its callback receives (&mut Frame<'_>, Rect, &MarkupElement) so it can render within the assigned region. Pass the storage to MarkupParser::try_new.
More complete markup and applications are in samples/tui-markup-sample and samples/tldr.
Migration from tui-rs
Replace tui with Ratatui 0.30.2 and Crossterm 0.29. MarkupParser and RendererStorage no longer have a backend type parameter because Ratatui's Frame is backend independent. Custom renderer callbacks now receive the element's area and node. render_ui(&mut Frame<'_>) remains available for embedding in an existing Ratatui application. Prefer try_new to get a parse or file error; the original new constructor remains available. The old detached event polling thread was replaced by a synchronous loop with terminal cleanup. Buffer and layout rendering use Ratatui's current APIs.
Run verification with:
cargo fmt --all -- --check
cargo check --all-targets
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
cargo doc --no-deps
cargo check --manifest-path samples/tui-markup-sample/Cargo.toml
cargo check --manifest-path samples/tldr/Cargo.toml
See DISCOVERY.md for migration findings and experiments. The project name is retained from the original library; tui-rs is historical context only.