retroglyph-ui
Immediate-mode drawing helpers for retroglyph: box
borders, filled panels, gauges, tables, lists, tab strips, buttons, sparklines, and a small
constraint-based layout splitter (split_h/split_v with ratatui-style
Fixed/Percent/Fill/Min/Max constraints, plus const-generic split_h_n/split_v_n
siblings that return [Rect; N] instead of allocating a Vec), plus hover/click/drag/focus
interaction tracking. Every widget (Panel, Gauge, Table, Sparkline, BoxBorder, List,
Tabs, Button, Scrollbar, ProgressBar, Modal, StatBar, Meter, Log, TextInput, ...)
is a builder struct that draws itself into a Surface (an area-relative view over a Grid) via
Widget/StatefulWidget and retains no state of its own; state that outlives one render call (a
selection index, a scroll offset, a text field's value and cursor) lives in
ListState/TextInputState instead. A handful of things that are genuinely just functions
(fill_rect, thumb_geometry/offset_for_pos, truncate/truncate_owned) stay free functions
rather than pretending to be widgets. Depends only on
retroglyph-core, so games that draw manually never
pull it in.
Alongside the widgets is the constraint-based Rect splitter above, with Flex alignment
(Start/End/Center/SpaceBetween/SpaceAround), similar to ratatui's
layout system. Fill(weight) claims a share of the leftover space proportional to weight relative
to the other Fill/Min/Max panes in the same split (Fill(1) reproduces plain equal
distribution).
Three more independent layers build on top:
Widget/StatefulWidgettraits let callers box or store heterogeneous widgets, e.g. aVec<Box<dyn Widget>>of panes to render each frame, backed byListStatefor selection and scroll position.AnimatedWidget, a sibling ofStatefulWidget, is for state that evolves with wall-clock time instead (ScrollState's momentum/rubber-band physics, aTween-driven transition), taking aFrame(the same oneApp::updatealready receives) alongside the state, so advancing and drawing happen in one call instead of two independently ordered ones.Scrollbarimplements it directly, tickingScrollStatebefore drawing the thumb at the result.ScrollState::applyfeeds a frame's resolvedResponse::scroll_deltastraight into the wheel impulse, so a scrollable widget doesn't have to re-derive wheel handling from raw mouse events (see13_combat_log, which wires wheel scrolling into itsLog/Scrollbarpair this way).BoxStyle, a Lip-Gloss-style box model (padding, border, margin) rendered into a standaloneGrid.Paragraph(behind theegcfeature) word-wraps text viaretroglyph-core'sTextLayoutand implements aMeasuretrait so a caller can size a pane to fit before rendering.join_h/join_vto compose severalGrids (e.g.BoxStyle::renderoutput) into one;retroglyph-core'sSurface::blitstamps the result onto a surface.Theme(Theme::DARK/Theme::LIGHT, or a caller-built palette): named color roles (border,accent,hover_bg, ...) that every widget with a style knob can pick up via a.theme(Theme)builder method, optionally: a manual.border_style(...)/etc. call after.theme(...)still wins, and nothing requires aThemeat all.
See the 09_widgets_dashboard and 15_outpost_dashboard examples for all of the above wired
together in one UI, 17_theme_switch for Theme::DARK/Theme::LIGHT switched live at runtime by a
keypress, or 19_weighted_fill for Fill(weight)'s proportional splits.
Quick start
use ;
use ;
let area = new;
let mut grid = new;
new.render;
A clickable widget (Button, Scrollbar, List, Tabs, or any InteractiveWidget) is shown
through Ui, which pairs one frame's Surface with an Interaction<Id> so a call site names an
area and an id once and gets both hit-testing and drawing from it:
use ;
use Surface;
use ;
let mut grid = new;
let mut interaction = new;
let clicked = interaction.frame;
assert!; // nothing clicked yet: no input was fed in
A control that exists but can't be used right now ("Save" with no game loaded, say) disables a whole
Ui subtree via Ui::enabled, not a per-widget flag: the returned Response still reports
hovered, so a call site can explain why, but never an activation:
use ;
use Surface;
use ;
let has_unsaved_changes = false;
let mut grid = new;
let mut interaction = new;
interaction.frame;
Features
dev
⚪ Optional.
Forwards retroglyph-core's dev feature, which forces development diagnostics on in a build that
would otherwise compile them out (see retroglyph_core::dev).
egc
⚪ Optional.
Forwards to retroglyph-core's egc feature.
Upgrades Paragraph's word-wrap (always available) to grapheme-cluster-aware correctness.
libm
⚪ Optional.
Uses retroglyph-core's libm feature (the no_std float backend: easing curves and tweens,
scrollbar geometry, gauge/sparkline/bar percentage rounding, scroll momentum decay) instead of
std's own float intrinsics. See std below; a build needs exactly one of the two.
serde
⚪ Optional.
Adds Serialize/Deserialize impls for Theme and Density, forwarding to retroglyph-core's
serde feature.
Theme round-trips through Color's own serde impl.
std
🟢 Enabled by default.
Enables retroglyph-core/std, whose float intrinsics back this crate's own float use (see libm
above for the no_std alternative).
Disabling this feature (--no-default-features) builds this crate no_std (requires an allocator
and one of std/libm; see the crate-level compile_error! in src/lib.rs).
See docs.rs for the full API, or the workspace README for the crate list.