Skip to main content

retroglyph_window/
lib.rs

1//! A shared layer for window-based backends (software, GL, wgpu).
2//!
3//! # Architecture
4//!
5//! [`retroglyph_core::backend::Input`] and [`retroglyph_core::backend::Output`] are two
6//! independent facets of [`Backend`](retroglyph_core::backend::Backend), which fits a terminal process
7//! (one type implements both) but not a window: there, an event loop owns input and a renderer
8//! owns output separately. This crate keeps that split ([`Presenter`](presenter::Presenter) is an
9//! `Output` supertrait, [`WindowBackend`](backend::WindowBackend) owns its own `Input` event
10//! queue) and reassembles both into one `Backend`:
11//!
12//! ```text
13//!               ┌─────────────────────────────┐
14//!               │     event loop (winit or     │
15//!               │      a custom driver)        │
16//!               └──────────────┬───────────────┘
17//!                    translated events
18//!                              │
19//!                              v
20//! ┌────────────────────────────────────────────────────┐
21//! │            WindowBackend<P: Presenter>              │
22//! │   (implements Backend: owns the input event queue,  │
23//! │              delegates output to P)                 │
24//! └───────────────────────┬──────────────────────────────┘
25//!                         │ draw / flush / resize / present
26//!                         v
27//!         ┌───────────────────────────────┐
28//!         │      P: Presenter              │
29//!         │  (retroglyph-software today;   │
30//!         │   wgpu/GL renderers planned)   │
31//!         └───────────────────────────────┘
32//! ```
33//!
34//! - [`Presenter`](presenter::Presenter) is `Output` plus the surface lifecycle
35//!   (`init_surface`/`resize_surface`/`present`/`cell_size`). Renderer crates implement only this
36//!   trait, which gives them `Output` for free.
37//! - <code>[WindowBackend](backend::WindowBackend)&lt;P: Presenter&gt;</code> implements `Output` (by delegating to `P`),
38//!   `Input` (via its own event queue), and the no-op default `Cursor` (windowed backends have no
39//!   text cursor), which together give it `Backend` generically.
40//! - The `winit` module (feature-gated, see below) drives the event loop that fills that queue
41//!   and calls `Presenter::present` each frame.
42//!
43//! # Features
44//!
45//! <!-- gen-features:start -->
46//! Default features: `winit`.
47//!
48//! ### `default-font`
49//!
50//! ⚪ Optional.
51//!
52//! Embeds the Unscii 16 default font (`font::unscii16`).
53//!
54//! Off by default so a consumer that supplies its own bitmap font pays nothing for the ~4 KB atlas;
55//! the graphical backends' own `default-font` features forward to this one.
56//!
57//! ### `dev`
58//!
59//! ⚪ Optional.
60//!
61//! Forwards `retroglyph-core`'s `dev` feature, which forces development diagnostics on in a build
62//! that would otherwise compile them out (see [`retroglyph_core::dev`]).
63//!
64//! Forwarded so a consumer of this crate can turn them on without adding a direct dependency on
65//! core just to reach the flag.
66//!
67//! ### `legacy-computing`
68//!
69//! ⚪ Optional.
70//!
71//! Embeds a generated block-elements/braille fallback font (`font::legacy_computing`): the 10
72//! quadrant, 60 sextant, and 256 braille glyphs CP437 (and so `unscii16`) has no mapping for.
73//!
74//! A separate opt-in from `default-font` rather than folded into it: this repertoire is a much more
75//! niche/specialized addition (subcell image rendering, braille density tricks) than the base text
76//! font, so a consumer that only wants CP437 text shouldn't pay for it. Computed at compile time by
77//! a `const fn`, so this adds no font asset and no new dependency.
78//!
79//! ### `testing`
80//!
81//! ⚪ Optional.
82//!
83//! Testing helpers for asserting glyph coverage (`testing::assert_glyphs_covered`,
84//! `testing::uncovered_glyphs`), so a consumer can check a `FontChain` actually draws the
85//! characters it cares about rather than silently falling back to the substituted solid block
86//! (retroglyph#1292).
87//!
88//! ### `tilesets`
89//!
90//! ⚪ Optional.
91//!
92//! Shared PNG sprite/tileset support (`tileset` + `sprite_cache` modules, issue #366).
93//!
94//! Both graphical backends' own `tilesets` features forward to this one.
95//!
96//! ### `winit`
97//!
98//! 🟢 Enabled by default.
99//!
100//! The winit event loop and event translation (`run`, `translate`, `run_windowed`/`run_app`).
101//!
102//! Renderer crates that only implement [`Presenter`](presenter::Presenter) can disable this and
103//! depend solely on `raw-window-handle`; loops other than winit (SDL2, tao, custom) bring their own
104//! driver against `Presenter` + `WindowBackend`.
105//! <!-- gen-features:end -->
106//!
107//! # Feature flags
108//!
109//! [`Presenter`](presenter::Presenter), [`WindowBackend`](backend::WindowBackend), and
110//! [`WindowHandle`](presenter::WindowHandle) depend only on
111//! [`raw-window-handle`](raw_window_handle) and are always available. The `winit` feature
112//! (default on) additionally provides the `winit` module: the event loop, event translation, and
113//! the `run_windowed`/`run_app` drivers. Disable it to implement or drive `Presenter` with a
114//! different windowing library (SDL2, tao, a custom loop) without pulling in winit.
115//!
116//! # DPI, scale, and the resize contract
117//!
118//! [`Presenter::cell_size`](presenter::Presenter::cell_size) returns the cell size in
119//! **physical pixels** (the same pixel space as `winit::dpi::PhysicalSize`), not
120//! logical/DPI-scaled ("CSS" or "point") pixels. This crate performs no automatic DPI scaling of
121//! it: nothing here changes `cell_size()` in response to a display's scale factor.
122//! `SoftwareRenderer`'s cell size, for example, is fixed at construction (glyph size × its
123//! integer `scale` config) and never changes on a
124//! [`Presenter::scale_factor_changed`](presenter::Presenter::scale_factor_changed)
125//! notification. A presenter that wants larger cells on a
126//! `HiDPI` display has to opt into that itself from `scale_factor_changed` (e.g. regenerating a
127//! font atlas at a new pixel density); until one does, the grid renders at a fixed physical
128//! pixel size on every display, `HiDPI` or not.
129//!
130//! Window resize is clamped to whole cells: a physical size that isn't an exact multiple of
131//! `cell_size()` has its sub-cell remainder truncated, not centered or cleared, and the OS
132//! window is never resized to compensate: see
133//! [`Presenter::resize_surface`](presenter::Presenter::resize_surface)'s doc comment for the full
134//! contract, including the unpainted trailing strip this can leave on screen.
135//!
136//! # Threading model
137//!
138//! The windowed drivers (`winit::run_windowed`, `winit::run_app`, and their `_with_proxy`
139//! variants) are single-threaded: the event loop, every [`Presenter`](presenter::Presenter) call,
140//! and the app closure/[`App`](retroglyph_core::app::App) callback all run on the one thread that
141//! calls `run_windowed`/`run_app`: the main thread, on platforms (e.g. macOS) that require it for
142//! windowing. Neither [`Presenter`](presenter::Presenter) nor [`WindowBackend`](backend::WindowBackend) carries a `Send`/`Sync` bound
143//! anywhere in this crate, and a presenter is free to hold thread-affine state accordingly
144//! (an `Rc`, a non-`Send` GPU context handle). The only supported way to reach the loop from
145//! another thread is `winit::EventProxy<T>`, which is `Send + Sync + Clone` for any
146//! `T: Send + 'static`: it does not give another thread direct access to the `Presenter` or
147//! `Terminal`. With the default `T = u64` (`winit::run_windowed_with_proxy`/
148//! `run_app_with_proxy`), the payload surfaces as an opaque
149//! [`Event::Custom`](retroglyph_core::event::Event::Custom); a custom `T`
150//! (`winit::run_windowed_with_typed_proxy`/`run_app_with_typed_proxy`) bypasses `Event` entirely
151//! and goes straight to a caller-supplied handler, since `Event::Custom` itself stays fixed to
152//! `u64`.
153
154#![doc(
155    html_logo_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
156)]
157#![doc(
158    html_favicon_url = "https://raw.githubusercontent.com/crates-lurey-io/retroglyph/main/docs/public/assets/logo.svg"
159)]
160#![cfg_attr(docsrs, feature(doc_cfg))]
161// A `pub mod` line's own outer doc comment and its target module's inner `//!` doc concatenate
162// into one rendered page, but intra-doc links in that combined block resolve against the scope
163// where the *outer* comment lives (this file, the crate root) rather than the module's own scope.
164// Every module doc below that also carries an outer doc comment on its `pub mod` line therefore
165// needs fully qualified links even for types the module defines itself, which then reads as
166// "redundant" from the module file's own point of view. Rather than track that split per link,
167// every intra-doc link in this crate is fully qualified and this lint is off crate-wide.
168#![allow(rustdoc::redundant_explicit_links)]
169
170pub mod atlas;
171// clippy::too_long_first_doc_paragraph is a known-noisy nursery lint (rust-lang/rust-clippy#13441):
172// it mis-attributes its span across this outer doc comment plus `backend`'s own inner module doc,
173// which grew past the threshold once its intra-doc links became fully qualified (retroglyph#1035).
174#[allow(clippy::too_long_first_doc_paragraph)]
175/// The generic [`Backend`](retroglyph_core::backend::Backend) for windowed presenters.
176pub mod backend;
177// See the `too_long_first_doc_paragraph` comment above `backend`: same noisy-lint mis-attribution.
178#[allow(clippy::too_long_first_doc_paragraph)]
179/// System clipboard read/write ([`Clipboard`](clipboard::Clipboard),
180/// [`SystemClipboard`](clipboard::SystemClipboard) on native targets).
181pub mod clipboard;
182// See the `too_long_first_doc_paragraph` comment above `backend`: same noisy-lint mis-attribution.
183#[allow(clippy::too_long_first_doc_paragraph)]
184/// Shared render-time glyph diagnostics ([`diagnostics::warn_notdef_glyph`]).
185pub mod diagnostics;
186pub mod font;
187/// Shared cell/surface pixel geometry ([`CellGeometry`](geometry::CellGeometry)).
188pub mod geometry;
189/// Canonical default colors ([`DEFAULT_FG`](palette::DEFAULT_FG),
190/// [`DEFAULT_BG`](palette::DEFAULT_BG)) shared by the graphical backends.
191pub mod palette;
192// See the `too_long_first_doc_paragraph` comment above `backend`: same noisy-lint mis-attribution.
193#[allow(clippy::too_long_first_doc_paragraph)]
194/// The [`Presenter`](presenter::Presenter) trait and [`WindowHandle`](presenter::WindowHandle).
195pub mod presenter;
196// See the `too_long_first_doc_paragraph` comment above `backend`: same noisy-lint mis-attribution.
197#[allow(clippy::too_long_first_doc_paragraph)]
198/// The [`PresenterBuilder`](presenter_builder::PresenterBuilder) trait shared by the software/GL/wgpu backend builders.
199pub mod presenter_builder;
200#[cfg(feature = "tilesets")]
201pub mod sprite_cache;
202// See the `too_long_first_doc_paragraph` comment above `backend`: same noisy-lint mis-attribution.
203#[allow(clippy::too_long_first_doc_paragraph)]
204/// Testing helpers for asserting glyph coverage against a [`font::FontChain`]
205/// ([`testing::assert_glyphs_covered`], [`testing::uncovered_glyphs`]).
206#[cfg(feature = "testing")]
207pub mod testing;
208#[cfg(feature = "tilesets")]
209pub mod tileset;
210/// Locates winit's `<canvas>` element via the DOM ([`web::winit_canvas`]).
211#[cfg(target_arch = "wasm32")]
212pub mod web;
213/// The winit event loop, event translation, and app drivers.
214#[cfg(feature = "winit")]
215pub mod winit;
216
217// Compile the code blocks in this crate's own README as doctests so its quick start is
218// type-checked on every test run and cannot silently rot. The `cfg(doctest)` gate keeps this out
219// of the rendered crate documentation: see `retroglyph-crossterm`'s matching include for the
220// same pattern applied to the workspace root README.
221#[cfg(doctest)]
222#[doc = include_str!("../README.md")]
223struct ReadmeDoctests;
224
225// No root re-exports below this line by design (retroglyph#1270), matching
226// `retroglyph-core` (retroglyph#1035): every public item lives at its module path.
227// `raw_window_handle` is the one exception: it re-exports a dependency's own public API so
228// presenters can name the handle traits without adding their own raw-window-handle dependency
229// that could drift to a different major version, not a same-crate convenience alias.
230pub use raw_window_handle;