Skip to main content

retroglyph/
lib.rs

1//! `retroglyph`: a 2D pseudographic terminal library, one dependency and one `use`.
2//!
3//! Add this crate and you get the double-buffered `Terminal`/`App` game loop, styled cells, text
4//! and layout helpers, and input events -- `app`, `color`, `event`, `frames`, `grid`, `layout`,
5//! `surface`, `terminal`, `text`, `tile`, `symbols` -- plus a [`prelude`] with the handful of
6//! names a program can't avoid, and one feature-gated module per backend (`crossterm`,
7//! `software`, `gl`, `wgpu`, `terminal_wasm`, `ui`). Writing a new backend instead of a game?
8//! Depend on [`retroglyph-core`](retroglyph_core) directly for its lower-level `backend`, `dev`,
9//! and `math` modules.
10//!
11//! # Quick start
12//!
13//! ```sh
14//! cargo add retroglyph
15//! ```
16//!
17//! ```rust,no_run
18//! use retroglyph::crossterm::Crossterm;
19//! use retroglyph::prelude::*;
20//!
21//! struct Game;
22//!
23//! impl App<Crossterm> for Game {
24//!     fn update(&mut self, term: &mut Terminal<Crossterm>, _frame: &Frame) -> Flow {
25//!         term.surface().put((5, 5), '@', Style::new().fg(Color::GREEN));
26//!
27//!         if let Some(Event::Key(k)) = term.poll(std::time::Duration::from_secs(1)) {
28//!             if k.code == KeyCode::Char('q') {
29//!                 return Flow::Exit;
30//!             }
31//!         }
32//!         Flow::Continue
33//!     }
34//! }
35//!
36//! fn main() -> std::io::Result<()> {
37//!     retroglyph::app::run(Crossterm::new()?, Game)
38//! }
39//! ```
40//!
41//! Want a native window or a browser tab instead of a real terminal? Enable the `software`, `gl`,
42//! or `wgpu` feature instead of (or alongside) `crossterm`: same `Terminal`/`App` contract, a
43//! different `Backend` type. See each backend module's own docs (and
44//! [`WindowConfig`]/[`run_app`]) for the windowed quick start.
45//!
46//! # Features
47//!
48//! <!-- gen-features:start -->
49//! Default features: `crossterm`, `ui`.
50//!
51//! ### `crossterm`
52//!
53//! 🟢 Enabled by default.
54//!
55//! Re-exports `retroglyph-crossterm` as [`crossterm`]: a real-terminal `Backend`
56//! via `crossterm`.
57//!
58//! ### `default-font`
59//!
60//! ⚪ Optional.
61//!
62//! Forwards each enabled backend's own `default-font` feature (an embedded Unscii 16 bitmap font),
63//! so a caller doesn't need to know which backend crate actually owns it.
64//!
65//! ### `gl`
66//!
67//! ⚪ Optional.
68//!
69//! Re-exports `retroglyph-gl` as [`gl`]: a GPU `Backend` via `glow` (OpenGL 3.3 native,
70//! WebGL2 wasm). Also pulls in the curated windowed re-exports (`WindowConfig`, `PresenterBuilder`,
71//! `Windowed`, `WindowedLaunchError`, `run_app`, `run_app_on`).
72//!
73//! ### `serde`
74//!
75//! ⚪ Optional.
76//!
77//! Adds `Serialize`/`Deserialize` impls to the curated types that support them (`Color`, `Style`,
78//! geometry, ..., plus [`ui::theme::Theme`]/`Density` when `ui` is also enabled). Forwards to
79//! `retroglyph-core`'s and `retroglyph-ui`'s own `serde` features; neither backend crate has one.
80//!
81//! ### `software`
82//!
83//! ⚪ Optional.
84//!
85//! Re-exports `retroglyph-software` as [`software`]: a CPU pixel `Backend` via
86//! `softbuffer`. Also pulls in the curated windowed re-exports (`WindowConfig`, `PresenterBuilder`,
87//! `Windowed`, `WindowedLaunchError`, `run_app`, `run_app_on`).
88//!
89//! ### `terminal-wasm`
90//!
91//! ⚪ Optional.
92//!
93//! Re-exports `retroglyph-terminal-wasm` as [`terminal_wasm`]: a browser `Backend` driven by
94//! pushed/pulled ANSI I/O (e.g. xterm.js). Its `#[wasm_bindgen]` FFI module only compiles for
95//! `target_arch = "wasm32"`, but the crate (and this re-export) build portably otherwise.
96//!
97//! ### `testing`
98//!
99//! ⚪ Optional.
100//!
101//! Enables [`TestHarness`] and its error, the published headless `App` driver for testing your own
102//! `App`. Forwards to `retroglyph-core`'s own `testing` feature.
103//!
104//! ### `tilesets`
105//!
106//! ⚪ Optional.
107//!
108//! Forwards each enabled backend's own `tilesets` feature (PNG sprite/tileset loading), so a
109//! caller doesn't need to know which backend crate actually owns it. Mirrors `default-font` above;
110//! see [`retroglyph_window::tileset`](https://docs.rs/retroglyph-window) for the `TilesetOptions`/
111//! `Codepage` config types that feature adds -- reach for `retroglyph-window` directly for those,
112//! same as any other finer-grained windowed control this facade doesn't curate.
113//!
114//! ### `tracing`
115//!
116//! ⚪ Optional.
117//!
118//! Forwards to `retroglyph-crossterm`'s `tracing` feature: instruments `draw`/`flush`/`poll_event`
119//! with `tracing` spans for profiling render/input time.
120//!
121//! ### `ui`
122//!
123//! 🟢 Enabled by default.
124//!
125//! Re-exports `retroglyph-ui` as [`ui`]: the immediate-mode widget/layout toolkit.
126//!
127//! ### `wgpu`
128//!
129//! ⚪ Optional.
130//!
131//! Re-exports `retroglyph-wgpu` as [`wgpu`]: a GPU `Backend` via `wgpu` (Vulkan,
132//! Metal, D3D12, WebGPU). Also pulls in the curated windowed re-exports (`WindowConfig`,
133//! `PresenterBuilder`, `Windowed`, `WindowedLaunchError`, `run_app`, `run_app_on`).
134//! <!-- gen-features:end -->
135#![doc(
136    html_logo_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
137)]
138#![doc(
139    html_favicon_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
140)]
141#![cfg_attr(docsrs, feature(doc_cfg))]
142
143// Compile the code blocks in both this crate's own README and the workspace root README as
144// doctests so the quick-start examples are type-checked on every test run and cannot silently
145// rot. The `cfg(doctest)` gate keeps these out of the rendered crate documentation. The workspace
146// root README's quick start now demonstrates this crate (previously `retroglyph-crossterm`,
147// before this crate existed), so it's doctested here instead.
148#[cfg(doctest)]
149#[doc = include_str!("../README.md")]
150struct ReadmeDoctests;
151
152#[cfg(doctest)]
153#[doc = include_str!("../../../README.md")]
154struct WorkspaceReadmeDoctests;
155
156pub use retroglyph_core::{
157    app, backend, color, event, frames, grid, layout, surface, symbols, terminal, text, tile,
158};
159
160/// The trait a backend's own builder implements to be driven end to end by
161/// [`launch`](Launch::launch): the user names the backend (`CrosstermOptions`, `Windowed<B>`, ...)
162/// and gets back that backend's own unwrapped error, rather than a facade-wide one. See this
163/// trait's own docs for why there is no unified error here (`retroglyph::LaunchError`, tracked by
164/// #430, is only needed if the facade grows an entry point that can return either backend's
165/// error).
166pub use retroglyph_core::app::Launch;
167
168// `retroglyph_core::testing` also holds `conformance` (`Observable`, `assert_output_contract`,
169// ...), the harness a *new backend* uses to prove it satisfies the `Output`/`Input`/`Cursor`
170// contracts. That's backend-author surface, not game-author surface, so only the two
171// game-facing items are re-exported here, individually, rather than the whole module.
172//
173// `RunError` is re-exported by its own name rather than a facade-specific alias: a future
174// unified driver error (`LaunchError`, tracked by #430) must not also be named `RunError`, or the
175// two would collide right here at `retroglyph::RunError`.
176#[cfg(feature = "testing")]
177pub use retroglyph_core::testing::{RunError, TestHarness};
178
179// A `pub mod` line's own doc comment and its target module's inner `//!` doc concatenate into one
180// rendered page (see `retroglyph-core`'s matching comment in its own `lib.rs`), and the combined
181// first paragraph here grows past this noisy nursery lint's threshold.
182#[allow(clippy::too_long_first_doc_paragraph)]
183/// The names a program cannot avoid, glob-importable in one line.
184pub mod prelude;
185
186/// A real-terminal [`Backend`](retroglyph_core::backend::Backend) via
187/// [`crossterm`](https://crates.io/crates/crossterm).
188#[cfg(feature = "crossterm")]
189pub use retroglyph_crossterm as crossterm;
190/// A GPU [`Backend`](retroglyph_core::backend::Backend) via
191/// [`glow`](https://crates.io/crates/glow): OpenGL 3.3 (native) and WebGL2 (wasm).
192#[cfg(feature = "gl")]
193pub use retroglyph_gl as gl;
194/// A CPU pixel [`Backend`](retroglyph_core::backend::Backend) via
195/// [`softbuffer`](https://crates.io/crates/softbuffer).
196#[cfg(feature = "software")]
197pub use retroglyph_software as software;
198/// A browser [`Backend`](retroglyph_core::backend::Backend) driven by pushed/pulled ANSI I/O
199/// (e.g. [xterm.js](https://xtermjs.org/)).
200#[cfg(feature = "terminal-wasm")]
201pub use retroglyph_terminal_wasm as terminal_wasm;
202/// The immediate-mode widget/layout toolkit: panels, gauges, tables, input/focus, theming,
203/// animation.
204#[cfg(feature = "ui")]
205pub use retroglyph_ui as ui;
206/// A GPU [`Backend`](retroglyph_core::backend::Backend) via
207/// [`wgpu`](https://crates.io/crates/wgpu): Vulkan, Metal, D3D12, and WebGPU.
208#[cfg(feature = "wgpu")]
209pub use retroglyph_wgpu as wgpu;
210
211// The curated windowed surface (issue #1203): a windowed backend's own quick-start needs a way
212// to build a window and drive its event loop, without reaching past this crate into
213// `retroglyph-window` for the whole `winit` module. `run_windowed`/`run_windowed_with_proxy`/
214// `run_windowed_with_typed_proxy`/`run_app_with_proxy`/`run_app_with_typed_proxy` stay
215// reachable only through `retroglyph_window::winit` directly: they're cross-thread event
216// injection power tools, not quick-start material.
217#[cfg(any(feature = "software", feature = "gl", feature = "wgpu"))]
218pub use retroglyph_window::presenter_builder::PresenterBuilder;
219#[cfg(any(feature = "software", feature = "gl", feature = "wgpu"))]
220pub use retroglyph_window::winit::{
221    WindowConfig, Windowed, WindowedLaunchError, run_app, run_app_on,
222};
223
224// ── `run_default`: one call, no per-app `#[cfg]` table ──────────────────────────────
225//
226// Four near-identical items below, one per backend, `#[cfg]`-gated so exactly one is compiled
227// for any given feature set (never zero, never more than one: each arm excludes every
228// higher-priority feature via `not(any(...))`, the same shape `retroglyph-examples`' own
229// `launch::<E>()` dispatch table already uses). A single generic function spanning every
230// backend isn't expressible instead: `Launch::Backend` differs per implementor, and stable Rust
231// has no way to write "`A` implements `App<B>` for whichever `B` this arm turns out to be" as
232// one bound (no non-lifetime higher-ranked trait bounds; see rust#108185, and `Launch`'s own
233// doc comment above for the same limitation). Priority: `software` > `gl` > `wgpu` >
234// `crossterm`, matching the tile-demo gallery's dispatch table this replaces (retroglyph#1295),
235// with `wgpu` slotted between the other two windowed backends and the real-terminal one.
236
237/// Picks a backend from this crate's enabled Cargo features and drives `app` on it, using that
238/// backend's own default configuration.
239///
240/// See the priority order above. Each backend is built with [`PresenterBuilder::new()`] for a
241/// windowed backend, paced by [`RunOptions::default()`](app::RunOptions), or
242/// [`Crossterm::builder()`](crossterm::Crossterm::builder) for the terminal one.
243///
244/// This is the zero-config fast path, not a general-purpose driver: an app that needs a specific
245/// grid size, window title, tileset, or pacing has outgrown "default" and should call that
246/// backend's own [`Launch::launch`] directly instead, e.g. `Windowed::new(builder, "My
247/// Game").launch(app, RunOptions::animated(60))` or `Crossterm::builder().launch(app, options)`.
248///
249/// Absent entirely (not a compile error) when none of `crossterm`, `software`, `gl`, or `wgpu`
250/// is enabled: there is no backend left to pick.
251///
252/// # Errors
253///
254/// Returns the picked backend's own [`Launch::Error`] if it fails to launch; see that backend's
255/// `Launch` impl for the exact conditions (a real terminal that can't enter raw mode, a
256/// presenter that fails to build, or a window/event loop that fails to start or run).
257#[cfg(feature = "software")]
258pub fn run_default<A>(
259    app: A,
260) -> Result<(), <Windowed<software::config::SoftwareBackendBuilder> as Launch>::Error>
261where
262    A: app::App<<Windowed<software::config::SoftwareBackendBuilder> as Launch>::Backend> + 'static,
263{
264    Windowed::new(
265        software::config::SoftwareBackendBuilder::new(),
266        "retroglyph",
267    )
268    .launch(app, app::RunOptions::default())
269}
270
271/// See [`run_default`]'s `software`-enabled overload. `gl` is the other GPU windowed backend;
272/// `software` wins if both happen to be enabled.
273///
274/// # Errors
275///
276/// See [`run_default`]'s `software`-enabled overload.
277#[cfg(all(feature = "gl", not(feature = "software")))]
278pub fn run_default<A>(
279    app: A,
280) -> Result<(), <Windowed<gl::config::GlBackendBuilder> as Launch>::Error>
281where
282    A: app::App<<Windowed<gl::config::GlBackendBuilder> as Launch>::Backend> + 'static,
283{
284    Windowed::new(gl::config::GlBackendBuilder::new(), "retroglyph")
285        .launch(app, app::RunOptions::default())
286}
287
288/// See [`run_default`]'s `software`-enabled overload. `wgpu` is the other GPU windowed backend;
289/// it loses to `software`/`gl` if either is also enabled.
290///
291/// # Errors
292///
293/// See [`run_default`]'s `software`-enabled overload.
294#[cfg(all(feature = "wgpu", not(any(feature = "software", feature = "gl"))))]
295pub fn run_default<A>(
296    app: A,
297) -> Result<(), <Windowed<wgpu::config::WgpuBackendBuilder> as Launch>::Error>
298where
299    A: app::App<<Windowed<wgpu::config::WgpuBackendBuilder> as Launch>::Backend> + 'static,
300{
301    Windowed::new(wgpu::config::WgpuBackendBuilder::new(), "retroglyph")
302        .launch(app, app::RunOptions::default())
303}
304
305/// See [`run_default`]'s `software`-enabled overload. `crossterm` is the real-terminal backend;
306/// it loses to any windowed backend that's also enabled.
307///
308/// # Errors
309///
310/// See [`run_default`]'s `software`-enabled overload.
311///
312/// # Examples
313///
314/// ```no_run
315/// use retroglyph::crossterm::Crossterm;
316/// use retroglyph::prelude::*;
317///
318/// struct Game;
319///
320/// impl App<Crossterm> for Game {
321///     fn update(&mut self, term: &mut Terminal<Crossterm>, _frame: &Frame) -> Flow {
322///         term.surface().put((5, 5), '@', Style::new().fg(Color::GREEN));
323///         Flow::Continue
324///     }
325/// }
326///
327/// fn main() -> Result<(), std::io::Error> {
328///     retroglyph::run_default(Game)
329/// }
330/// ```
331#[cfg(all(
332    feature = "crossterm",
333    not(any(feature = "software", feature = "gl", feature = "wgpu"))
334))]
335pub fn run_default<A>(app: A) -> Result<(), <crossterm::CrosstermOptions as Launch>::Error>
336where
337    A: app::App<<crossterm::CrosstermOptions as Launch>::Backend> + 'static,
338{
339    crossterm::Crossterm::builder().launch(app, app::RunOptions::default())
340}