Skip to main content

retroglyph_core/
lib.rs

1//! retroglyph-core: the `no_std`-compatible foundation of retroglyph.
2//!
3//! Grid, tile, style, color, text, terminal, and event types, plus the
4//! [`Output`](crate::backend::Output)/[`Input`](crate::backend::Input)/[`Cursor`](crate::backend::Cursor) backend
5//! facets (bundled together as [`Backend`](crate::backend::Backend)) and the dependency-free
6//! [`Headless`](crate::backend::Headless) test backend, and the `App`/`Flow`/`Frame` game loop contract.
7//! Platform backends (`retroglyph-crossterm`, `retroglyph-software`) and drawing helpers
8//! (`retroglyph-ui`) are separate crates that depend on this one.
9//!
10//! # Features
11//!
12//! <!-- gen-features:start -->
13//! Default features: `egc`, `std`.
14//!
15//! ### `dev`
16//!
17//! ⚪ Optional.
18//!
19//! Forces `BuildMode::Dev` on in a build that would otherwise resolve to `Release`.
20//!
21//! Can be used so an optimized build still reports development diagnostics (see the [`dev`]
22//! module).
23//!
24//! ### `egc`
25//!
26//! 🟢 Enabled by default.
27//!
28//! Enables grapheme-cluster-aware text handling (via `unicode-segmentation`) for EGC-correct cell
29//! diffing and layout.
30//!
31//! ### `libm`
32//!
33//! ⚪ Optional.
34//!
35//! Uses `libm`'s software float implementation (`roundf`/`fmaf`/`sinf`/`cosf`/`powf`) for
36//! the separable [`BlendMode`](crate::grid::BlendMode) channel math, via this crate's own
37//! `math` shim -- the `no_std` side of that split. See `std` below for the alternative that prefers
38//! the platform's own float intrinsics when available; a build needs exactly one of the two.
39//!
40//! ### `serde`
41//!
42//! ⚪ Optional.
43//!
44//! Adds `Serialize`/`Deserialize` impls for [`Color`](crate::color::Color), [`Style`](crate::color::Style), `Size`,
45//! `Offset`, and (via `ixy`) `Pos`/`Rect`, so a config file can round-trip a saved camera position,
46//! window geometry, sub-cell pixel offset, or theme color.
47//!
48//! [`Color`](crate::color::Color) serializes through its `Display`/`FromStr` round trip (e.g. `"bright-red"`,
49//! `"#ff8000"`) rather than a derived structural form, so hand-edited TOML/JSON stays legible.
50//!
51//! ### `std`
52//!
53//! 🟢 Enabled by default.
54//!
55//! Enables `gem/std` and `alpha-blend/std`, and uses `std`'s float intrinsics (via this crate's
56//! `math` shim) instead of `libm`'s software implementation for the separable
57//! [`BlendMode`](crate::grid::BlendMode) channel math.
58//!
59//! Disabling this feature (`--no-default-features`) builds this crate `no_std`, and then needs
60//! `libm` above as the float backend instead: see the crate-level `compile_error!` in `src/lib.rs`.
61//!
62//! ### `testing`
63//!
64//! ⚪ Optional.
65//!
66//! Enables `testing`'s `TestHarness`, which drives an [`App`](crate::app::App) against
67//! [`Headless`](crate::backend::Headless) for tests, with
68//! synthetic input queuing and frame-settling helpers.
69//!
70//! Test-only surface, `no_std` + `alloc` compatible, off by default so it never ships in a release
71//! build by accident.
72//! <!-- gen-features:end -->
73//!
74//! # Architecture
75//!
76//! [`Terminal<B>`](crate::terminal::Terminal) owns a double-buffered [`Grid`](crate::grid::Grid) and the
77//! [`Backend`](crate::backend::Backend) lifecycle (resize, present, events). Drawing itself goes entirely
78//! through [`Surface`](crate::surface::Surface), handed out by
79//! [`Terminal::draw`](crate::terminal::Terminal::draw)/[`Terminal::surface`](crate::terminal::Terminal::surface):
80//! a game calls `term.draw(|s| { s.put(...); ... })`
81//! once per frame, and [`present`](crate::terminal::Terminal::present) diffs the current frame against the
82//! previous one, sending only changed cells to the [`Backend`](crate::backend::Backend). `B` is the only thing that
83//! changes between a headless test and a real window or terminal:
84//!
85//! ```text
86//!               ┌───────────────────────────┐
87//!               │      App::update(...)      │  game logic, once, generic over B
88//!               └──────────────┬─────────────┘
89//!                              │ term.draw(|s| ...): writes through Surface
90//!                              ▼
91//!               ┌───────────────────────────┐
92//!               │       Terminal<B>          │  double-buffered Grid, cell diff
93//!               └──────────────┬─────────────┘
94//!                              │ draw / draw_layers / poll_event
95//!                              ▼
96//!               ┌───────────────────────────┐
97//!               │  B: Output + Input + Cursor │  the only piece that swaps out
98//!               └──────────────┬─────────────┘
99//!                              │
100//!        ┌─────────────────────┼─────────────────────┐
101//!        ▼                     ▼                      ▼
102//!  Headless (here)      Crossterm                SoftwareRenderer
103//!  in-memory grid,      (retroglyph-crossterm)   (retroglyph-software)
104//!  synthetic events     real TTY, ANSI output    winit window, pixels
105//! ```
106//!
107//! [`Headless`](crate::backend::Headless) stores presented content in memory and lets tests inject
108//! synthetic [`Event`](crate::event::Event)s with [`Headless::push_event`](crate::backend::Headless::push_event);
109//! nothing here talks to a real terminal or window. Swapping `Headless` for
110//! `Crossterm` or `SoftwareRenderer` changes only the `B` type parameter --
111//! `App` implementations, [`Terminal`](crate::terminal::Terminal) calls, and game logic are unchanged.
112//! `run_blocking` drives `Terminal<Headless>` and `Terminal<Crossterm>`
113//! identically; the software backend's windowed loop drives `Terminal<SoftwareRenderer>`
114//! through the same [`App`](crate::app::App) contract, inverted because winit owns the
115//! event loop instead of handing control back to a driver function.
116//!
117//! See `examples/headless.rs` (`cargo run -p retroglyph-core --example
118//! headless`) for the smallest possible use of [`Headless`](crate::backend::Headless), depending on
119//! nothing but this crate.
120#![cfg_attr(not(feature = "std"), no_std)]
121#![cfg_attr(docsrs, feature(doc_cfg))]
122// A `pub mod` line's own outer doc comment and its target module's inner `//!` doc concatenate
123// into one rendered page, but intra-doc links in that combined block resolve against the scope
124// where the *outer* comment lives (this file, the crate root) rather than the module's own scope.
125// Every module doc below that also carries an outer doc comment on its `pub mod` line therefore
126// needs fully qualified links even for types the module defines itself, which then reads as
127// "redundant" from the module file's own point of view. Rather than track that split per link,
128// every intra-doc link in this crate is fully qualified and this lint is off crate-wide.
129#![allow(rustdoc::redundant_explicit_links)]
130extern crate alloc;
131
132// A float backend is not optional (retroglyph#903): the separable `BlendMode` channel math
133// dispatches through `crate::math`, which has nothing to dispatch *to* without one, and
134// `Color`'s color-space conversions go through `gem/space`, which needs `gem/std` or `gem/libm`
135// for the same reason. Failing here names the two features that fix it, ahead of the same build
136// failing as an unresolved `libm::` path inside `math.rs` or inside `gem::space`'s own
137// `compile_error!`.
138#[cfg(not(any(feature = "std", feature = "libm")))]
139compile_error!("retroglyph-core 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
149/// The `App`-driven game loop.
150pub mod app;
151// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution,
152// here because this module's own first doc paragraph grew past the threshold once its intra-doc
153// links became fully qualified (retroglyph#1035).
154#[allow(clippy::too_long_first_doc_paragraph)]
155/// Pluggable rendering backends.
156pub mod backend;
157pub mod color;
158// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution.
159#[allow(clippy::too_long_first_doc_paragraph)]
160/// Which diagnostics a build compiles in.
161pub mod dev;
162pub mod event;
163// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution.
164#[allow(clippy::too_long_first_doc_paragraph)]
165/// `FrameClock`/`FrameStats` accumulators for the `App`/`Frame` game loop.
166pub mod frames;
167pub mod grid;
168pub mod layout;
169// `pub` so `retroglyph-ui` can share this crate's one std-or-libm dispatch point instead of
170// vendoring its own copy, `#[doc(hidden)]` so that sharing costs no public API surface:
171// `cargo-semver-checks` ignores hidden items (see the module's own doc comment for the traps that
172// come with that). Never add a `pub use` that re-exports its contents through a non-hidden path,
173// and never `#[deprecated]` it, both of which would make it public API again despite the hiding.
174#[doc(hidden)]
175pub mod math;
176// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution.
177#[allow(clippy::too_long_first_doc_paragraph)]
178/// The one grid-drawing primitive: an area-clipped, single-layer view over a [`Grid`](crate::grid::Grid).
179pub mod surface;
180#[allow(clippy::too_long_first_doc_paragraph)]
181/// Border, gridline, and partial-block `char` data shared by widgets and backends.
182pub mod symbols;
183pub mod terminal;
184// See the `too_long_first_doc_paragraph` comment above `animate`: same noisy-lint mis-attribution.
185#[allow(clippy::too_long_first_doc_paragraph)]
186/// Headless test harness driving an `App` with synthetic input.
187#[cfg(feature = "testing")]
188pub mod testing;
189pub mod text;
190/// The atomic drawable unit (glyph, style, sub-cell offsets).
191pub mod tile;
192
193// No root re-exports below this line by design (retroglyph#1035): every public item lives at its
194// module path, matching `ratatui-core`. `dev_only!` (`dev.rs`) and `spans!` (`text.rs`) still
195// resolve at the crate root regardless, since `#[macro_export]` always places a macro there; that's
196// a macro-export constraint, not a re-export choice.