Expand description
§tuile
Textual-grade building blocks for ratatui: a ported design system (themes to derived colour roles), 109 widget types with keyboard and mouse handling built in, tweened animation, overlays (modals, toasts, palettes, dropdowns), AI/LLM chat components, a 102-spinner catalog and an app runtime that only redraws when something moves.
§Import
[dependencies]
tuile = "0.3"use tuile::prelude::*; is the one import: every widget, Theme/theme, draw/layout
helpers, anim, and the ratatui + crossterm types you touch (Rect, Buffer, Frame,
Event, KeyCode…). The crates themselves are re-exported as ratatui_core and
crossterm so an app needs no version pins of its own.
The dependency is ratatui-core, not the ratatui facade: upstream splits the
project so that applications use ratatui and widget libraries use ratatui-core. The types
are identical, so these widgets render into a Frame from a ratatui app unchanged.
use tuile::prelude::*;
use std::time::Instant;
struct Demo { on: SwitchState }
impl App for Demo {
fn draw(&mut self, frame: &mut Frame, now: Instant) {
let area = center(frame.area(), 20, 3);
Switch::new().now(now).focused(true).render(area, frame.buffer_mut(), &mut self.on);
}
fn event(&mut self, ev: Event, now: Instant) -> Flow {
if let Event::Key(k) = &ev { if k.code == KeyCode::Char('q') { return Flow::Quit; } }
self.on.handle(&ev);
Flow::Continue
}
fn animating(&self, now: Instant) -> bool { self.on.animating(now) }
}§Reuse and customize
Every widget is a builder (per-frame configuration, consumed by render) plus a
<Name>State (values, hover, tweens, scroll: plain Clone + Debug data with public
fields) that implements core::Interactive: state.handle(&event) → core::Outcome.
- Theme.
theme::set_by_name/theme::setswitch every widget at once;.theme(&Theme)overrides one. Build palettes withtheme::ThemeSpec::newandtheme::Theme::resolve; every derived role (text_muted,border_blurred,cursor_bg…) is a publicRgbfield. - Time. Pass the frame
Instantto.now(..). Looping animations phase fromanim::EPOCH;.elapsed(secs)makes them deterministic for tests and screenshots. - Content. Builders borrow (
&str,&[T],&'static SpinnerDef); nothing is copied until it is drawn. Custom spinners:SpinnerDef::new("name", ms, &frames)orSpinner::frames(&frames, ms). Custom looks:draw::Borderstyles,theme::Variants. - Extend. New widgets use the same public primitives (
draw,layout,anim,core::HitBox,core::Focus); the contract is at https://osyna.github.io/tuile/reference/widget-contract.html.
Re-exports§
pub use crossterm;pub use ratatui_core;pub use ratatui_crossterm;
Modules§
- anim
- Time-based animation: easing curves, retargetable tweens, pulses and blinks.
Everything samples an explicit
Instantso rendering stays pure and testable. - core
- Shared vocabulary every widget speaks: event outcomes, interaction state (
Look), focus rings, hit boxes for mouse handling, and small key/mouse helpers. - draw
- Buffer-level drawing primitives shared by every widget: clipped text, fills, the full set of Textual border styles (with titles), colour blending, block glyph tables and text wrapping/truncation. All functions clip to the buffer, so callers never index out of range.
- fuzzy
- Fuzzy subsequence matching for command palettes, filters and pickers.
- layout
- Small layout helpers on top of
ratatui_core::layout: centring, padding, fixed rows/columns, and anOverlayqueue for things drawn after everything else (dropdowns, tooltips). - prelude
- Everything an app usually needs.
- runtime
- The app runner: terminal setup/teardown (with panic-safe restore), mouse capture, an event loop that only redraws at 60 fps while something animates, and a local clock.
- term
- Terminal capability detection: colour depth and Unicode support.
- theme
- Textual’s design system, ported: a small base palette expands into every derived role
(CIE-Lab shades,
auto N%contrast text, panel/boost, cursor/border/scrollbar colours). - widgets
- Every widget, re-exported flat:
use tuile::widgets::*;.