Skip to main content

retroglyph_ui/
lib.rs

1//! Immediate-mode drawing helpers over a [`Rect`](retroglyph_core::grid::Rect).
2//!
3//! Box borders, filled panels, gauges, tables, sparklines, and a small
4//! constraint-based [`Rect`](retroglyph_core::grid::Rect) splitter with
5//! ratatui-style `Fixed`/`Percent`/`Fill`/`Min`/`Max` constraints and `Flex`
6//! alignment ([`layout`]).
7//!
8//! Every widget ([`widget`]) is a builder struct that draws itself into a
9//! [`Grid`](retroglyph_core::grid::Grid) via [`Widget`](widget::Widget)/[`StatefulWidget`](widget::StatefulWidget) and
10//! retains no state of its own: state that outlives one render call (a
11//! selection index, a scroll offset, a text field's value and cursor)
12//! lives in [`ListState`](state::ListState)/[`TextInputState`](state::TextInputState) instead. A
13//! handful of things that are genuinely just functions ([`fill_rect`](draw::fill_rect),
14//! [`thumb_geometry`](draw::thumb_geometry)/[`offset_for_pos`](draw::offset_for_pos) in [`draw`]; [`truncate`](text::truncate)/[`truncate_owned`](text::truncate_owned) in
15//! [`text`]) stay free functions rather than pretending to be widgets. Three more independent
16//! layers build on top:
17//!
18//! - [`Widget`](widget::Widget)/[`StatefulWidget`](widget::StatefulWidget) ([`widget`]) render into a [`Surface`],
19//!   an area-relative, single-layer view over a [`Grid`](retroglyph_core::grid::Grid), and let
20//!   callers box or store heterogeneous widgets, e.g. a `Vec<Box<dyn Widget>>` of panes to
21//!   render each frame, with no `Backend` type parameter, since drawing touches nothing but
22//!   cells. [`AnimatedWidget`](widget::AnimatedWidget) is `StatefulWidget`'s sibling for state that evolves with
23//!   wall-clock time (e.g. [`ScrollState`](state::ScrollState)'s momentum physics) instead of only in response to
24//!   input; see its own docs. [`InteractiveWidget`](widget::InteractiveWidget) is the sibling trait for widgets that also
25//!   read a [`Response`](interact::Response) (`Button`, `Scrollbar`, `List`, `Tabs`). For a
26//!   bordered box drawn straight into that `Surface` -- with titles, [`BorderType`](widget::BorderType)
27//!   glyph sets, `padding`, and theming -- reach for [`Panel`](widget::Panel) first; it's the
28//!   default answer to "draw me a box".
29//! - [`Interaction`](interact::Interaction) ([`interact`]) for hover/click/drag/focus tracking
30//!   without a retained widget tree: the sibling of [`ListState`](state::ListState) for
31//!   widgets that don't have a natural selection index of their own. [`Ui`](ui::Ui) pairs one frame's
32//!   [`Surface`] with an `Interaction`, via [`Interaction::frame`](interact::Interaction::frame): [`Ui::show`](ui::Ui::show) is how an
33//!   [`InteractiveWidget`](widget::InteractiveWidget) gets hit-tested and drawn from the one area/id a call site names.
34//! - [`BoxStyle`](style::BoxStyle) ([`style`]) is the standalone alternative to
35//!   [`Panel`](widget::Panel): a Lip-Gloss-style box model (padding, border, margin) rendered
36//!   into its own `Grid`, independent of any `Surface`/`Backend`/`Terminal`. Reach for it instead
37//!   of `Panel` when composing a box outside a frame, laying several boxes out with `join_h`/
38//!   `join_v` below, or needing `margin` (space outside the border, which `Panel` has no concept
39//!   of); it has no titles, `BorderType`, or theming.
40//! - [`join_h`](block::join_h)/[`join_v`](block::join_v) ([`block`]) to compose several `Grid`s (e.g. `BoxStyle::render`
41//!   output) into one; `retroglyph_core::surface::Surface::blit` stamps the result onto a surface.
42//! - [`Theme`](theme::Theme) ([`theme`]) for named color roles (an app picks
43//!   [`Theme::DARK`](theme::Theme::DARK)/[`Theme::LIGHT`](theme::Theme::LIGHT), or builds its own), independent of
44//!   how the app decides which one is active.
45//! - [`HalfBlockCanvas`](canvas::HalfBlockCanvas)/[`QuadrantCanvas`](canvas::QuadrantCanvas)/
46//!   [`SextantCanvas`](canvas::SextantCanvas)/[`BrailleCanvas`](canvas::BrailleCanvas) ([`canvas`]) for sub-cell
47//!   drawing: plot points at higher-than-one-per-cell resolution, then read a frame's worth of
48//!   glyphs back out, one per cell.
49//!
50//! This crate is itself optional: games that draw manually depend only on
51//! `retroglyph-core`.
52//!
53//! This crate is `no_std`-compatible: disable the `std` feature and enable `libm` instead (also
54//! requires an allocator). See the `std` and `libm` features below.
55//!
56//! # Features
57//!
58//! <!-- gen-features:start -->
59//! Default features: `std`.
60//!
61//! ### `dev`
62//!
63//! ⚪ Optional.
64//!
65//! Forwards `retroglyph-core`'s `dev` feature, which forces development diagnostics on in a build
66//! that would otherwise compile them out (see [`retroglyph_core::dev`](retroglyph_core::dev)).
67//!
68//! ### `egc`
69//!
70//! ⚪ Optional.
71//!
72//! Forwards to `retroglyph-core`'s `egc` feature.
73//!
74//! Upgrades [`Paragraph`](crate::widget::Paragraph)'s word-wrap (always available) to grapheme-cluster-aware correctness.
75//!
76//! ### `libm`
77//!
78//! ⚪ Optional.
79//!
80//! Uses `retroglyph-core`'s `libm` feature (the `no_std` float backend: easing curves and
81//! tweens, scrollbar geometry, gauge/sparkline/bar percentage rounding, scroll momentum decay)
82//! instead of `std`'s own float intrinsics. See `std` below; a build needs exactly one of the two.
83//!
84//! ### `serde`
85//!
86//! ⚪ Optional.
87//!
88//! Adds `Serialize`/`Deserialize` impls for [`Theme`](crate::theme::Theme) and `Density`, forwarding to
89//! `retroglyph-core`'s `serde` feature.
90//!
91//! [`Theme`](crate::theme::Theme) round-trips through `Color`'s own `serde` impl.
92//!
93//! ### `std`
94//!
95//! 🟢 Enabled by default.
96//!
97//! Enables `retroglyph-core/std`, whose float intrinsics back this crate's own float use (see
98//! `libm` above for the `no_std` alternative).
99//!
100//! Disabling this feature (`--no-default-features`) builds this crate `no_std` (requires an
101//! allocator and one of `std`/`libm`; see the crate-level `compile_error!` in `src/lib.rs`).
102//!
103//! ### `testing`
104//!
105//! ⚪ Optional.
106//!
107//! Enables `testing`'s `WidgetHarness`, the sibling of `retroglyph-core`'s
108//! `testing::TestHarness` for driving a bare `Interaction<Id>`/`Ui` (no `App` required) against
109//! `Headless` for tests, with synthetic input queuing and a `step` that drains it.
110//!
111//! Test-only surface, off by default so it never ships in a release build by accident. `Headless`
112//! is unconditionally available in `retroglyph-core`, so this needs no feature forwarding there.
113//! <!-- gen-features:end -->
114
115#![cfg_attr(not(feature = "std"), no_std)]
116#![doc(
117    html_logo_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
118)]
119#![doc(
120    html_favicon_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
121)]
122#![cfg_attr(docsrs, feature(doc_cfg))]
123// A `pub mod` line's own outer doc comment and its target module's inner `//!` doc concatenate
124// into one rendered page, but intra-doc links in that combined block resolve against the scope
125// where the *outer* comment lives (this file, the crate root) rather than the module's own scope.
126// Every module doc below that also carries an outer doc comment on its `pub mod` line therefore
127// needs fully qualified links even for types the module defines itself, which then reads as
128// "redundant" from the module file's own point of view. Rather than track that split per link,
129// every intra-doc link in this crate is fully qualified and this lint is off crate-wide.
130#![allow(rustdoc::redundant_explicit_links)]
131extern crate alloc;
132
133// This crate's float use -- easing curves and tweens, scrollbar geometry, gauge/sparkline/bar
134// percentage rounding, scroll momentum decay -- is load-bearing across the widget set, not an
135// opt-in extra. There is no equivalent of "the affected module just isn't compiled in", so a
136// build with neither backend fails loudly here instead of hitting `retroglyph_core::math`'s own
137// unresolved-`libm` error deep in a call site.
138#[cfg(not(any(feature = "std", feature = "libm")))]
139compile_error!("retroglyph-ui needs a float backend: enable `std` or `libm`.");
140
141// Compile the code blocks in this crate's own README as doctests so its quick start is
142// type-checked on every test run and cannot silently rot. The `cfg(doctest)` gate keeps this out
143// of the rendered crate documentation: see `retroglyph-crossterm`'s matching include for the
144// same pattern applied to the workspace root README.
145#[cfg(doctest)]
146#[doc = include_str!("../README.md")]
147struct ReadmeDoctests;
148
149pub mod align;
150// A `pub mod` line's own doc comment concatenates with the module's inner `//!` doc and resolves
151// against the crate root, so this module's own types need to be qualified in it even though the
152// module defines them itself; that inflation is what pushes the paragraph below past the
153// `too_long_first_doc_paragraph` threshold (retroglyph#1273).
154#[allow(clippy::too_long_first_doc_paragraph)]
155/// Time-driven value animation: easing curves, a stateful `Tween`, and a periodic oscillator.
156///
157/// The trig-based easing curves and the oscillator's sine wave go through `retroglyph_core`'s
158/// `math` shim, so they use `std`'s float intrinsics or `libm`'s software implementation
159/// depending on which backend feature is on.
160pub mod animate;
161pub mod block;
162/// A scrolling viewport into a world larger than the screen.
163pub mod camera;
164// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution.
165#[allow(clippy::too_long_first_doc_paragraph)]
166/// Sub-cell drawing: plot points/pixels at higher-than-one-per-cell resolution and read them
167/// back as glyphs, via `retroglyph_core::symbols`'s quantizers.
168pub mod canvas;
169pub mod draw;
170pub mod interact;
171pub mod layout;
172pub mod state;
173pub mod style;
174#[cfg(feature = "testing")]
175pub mod testing;
176pub mod text;
177pub mod theme;
178pub mod ui;
179pub mod widget;
180
181// No root re-exports below this line by design (retroglyph#1273, matching retroglyph-core's
182// retroglyph#1035): every public item lives at its module path. `retroglyph_core::surface`'s
183// types are the one exception, kept per retroglyph#1035's own reasoning for this line: they spare
184// callers from tracking which crate a type lives in, which is a different, legitimate concern
185// from this crate's own root re-exports.
186pub use retroglyph_core::surface::{Layer, StyledSurface, Surface};