retroglyph_core/backend/mod.rs
1//! Pluggable rendering backends.
2//!
3//! The [`Output`](crate::backend::Output), [`Input`](crate::backend::Input), and [`Cursor`](crate::backend::Cursor) traits (plus the [`Backend`](crate::backend::Backend) bundle that ties them
4//! together) and the dependency-free [`Headless`](crate::backend::Headless) test backend live here. Platform backends
5//! (crossterm, software/winit) are separate crates (`retroglyph-crossterm`, `retroglyph-software`)
6//! that depend on this one and implement these traits.
7
8pub mod headless;
9
10pub use headless::Headless;
11
12use crate::color::Tint;
13use crate::event::Event;
14use crate::grid::{Pos, Size};
15use crate::tile::Tile;
16use core::time::Duration;
17
18/// Associated error type used by all fallible backend methods.
19///
20/// Backends that are infallible (e.g. `Headless`, `SoftwareRenderer`) use
21/// [`core::convert::Infallible`]. Fallible backends (e.g. `Crossterm`) use
22/// [`std::io::Error`].
23///
24/// Requiring [`core::error::Error`] rather than just [`Display`](core::fmt::Display) +
25/// [`Debug`](core::fmt::Debug) lets generic code convert `B::Error` into `Box<dyn Error>` or any
26/// error-trait-based caller error with a plain `?`, and exposes `source()` chains for concrete
27/// error types that wrap an inner error.
28///
29/// # Examples
30///
31/// ```
32/// use retroglyph_core::backend::BackendError;
33/// use core::fmt;
34///
35/// #[derive(Debug)]
36/// struct MyBackendError;
37///
38/// impl fmt::Display for MyBackendError {
39/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40/// write!(f, "my backend failed")
41/// }
42/// }
43///
44/// impl core::error::Error for MyBackendError {}
45/// impl BackendError for MyBackendError {}
46/// ```
47pub trait BackendError: core::error::Error {}
48
49impl BackendError for core::convert::Infallible {}
50#[cfg(feature = "std")]
51impl BackendError for std::io::Error {}
52
53/// One cell handed to a backend at draw time: the tile, plus the state that does not fit in one.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55#[non_exhaustive]
56pub struct DrawCell<'a> {
57 /// Which layer this cell belongs to. Always `0` for cells arriving through
58 /// [`Output::draw`](crate::backend::Output::draw).
59 pub layer: u8,
60 /// Where the cell sits in the grid.
61 pub pos: Pos,
62 /// The tile itself.
63 pub tile: &'a Tile,
64 /// The tile's full grapheme cluster, or `None` to render [`Tile::glyph`](crate::tile::Tile::glyph) alone.
65 ///
66 /// `Some` only for multi-codepoint clusters (combining marks, ZWJ sequences), and **only
67 /// ever `Some` when the `egc` feature is enabled**: without it `Grid` never populates the
68 /// side table, so a backend that does not support `egc` can ignore this entirely and render
69 /// from [`Tile::glyph`](crate::tile::Tile::glyph).
70 pub grapheme: Option<&'a str>,
71 /// How a pixel backend recolours this cell's sprite.
72 ///
73 /// [`Tint::None`](crate::color::Tint::None) for the overwhelming majority of cells. Cell backends have no sprite to
74 /// recolour and ignore it; see [`Tint`](crate::color::Tint).
75 pub tint: Tint,
76}
77
78impl<'a> DrawCell<'a> {
79 /// A cell on layer 0 with no grapheme text and no tint: the shape almost every test and
80 /// cell backend wants.
81 #[must_use]
82 pub const fn new(pos: Pos, tile: &'a Tile) -> Self {
83 Self {
84 layer: 0,
85 pos,
86 tile,
87 grapheme: None,
88 tint: Tint::None,
89 }
90 }
91
92 /// [`new`](Self::new) on an explicit layer.
93 #[must_use]
94 pub const fn on_layer(layer: u8, pos: Pos, tile: &'a Tile) -> Self {
95 Self {
96 layer,
97 pos,
98 tile,
99 grapheme: None,
100 tint: Tint::None,
101 }
102 }
103
104 /// This cell with `grapheme` as its full cluster text.
105 #[must_use]
106 pub const fn with_grapheme(mut self, grapheme: Option<&'a str>) -> Self {
107 self.grapheme = grapheme;
108 self
109 }
110
111 /// This cell with `tint` applied to its sprite.
112 #[must_use]
113 pub const fn with_tint(mut self, tint: Tint) -> Self {
114 self.tint = tint;
115 self
116 }
117}
118
119/// Draws grid content to a display and reports its dimensions.
120///
121/// This is the only one of the three backend facets ([`Output`](crate::backend::Output), [`Input`](crate::backend::Input), [`Cursor`](crate::backend::Cursor)) that's
122/// fallible: writing to a real display can fail (a broken pipe, a closed terminal, a lost
123/// surface), so every mutating method here returns `Result<(), Self::Error>`.
124///
125/// # Examples
126///
127/// ```
128/// use retroglyph_core::backend::{DrawCell, Output};
129/// use retroglyph_core::grid::Size;
130///
131/// struct NullOutput;
132///
133/// impl Output for NullOutput {
134/// type Error = core::convert::Infallible;
135///
136/// fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
137/// where
138/// I: Iterator<Item = DrawCell<'a>>,
139/// {
140/// Ok(())
141/// }
142///
143/// fn flush(&mut self) -> Result<(), Self::Error> {
144/// Ok(())
145/// }
146///
147/// fn size(&self) -> Size {
148/// Size::new(4, 2)
149/// }
150///
151/// fn clear(&mut self) -> Result<(), Self::Error> {
152/// Ok(())
153/// }
154/// }
155/// ```
156pub trait Output {
157 /// Error type returned by fallible operations.
158 type Error: BackendError;
159
160 /// Draw changed cells to the output surface, layer 0 only.
161 ///
162 /// Every cell arrives as a [`DrawCell`](crate::backend::DrawCell), which carries the out-of-line state a [`Tile`](crate::tile::Tile)
163 /// cannot: its full grapheme cluster and its tint. Both live in a side table on
164 /// [`Grid`](crate::grid::Grid)
165 /// rather than in the tile, so a backend that needs either must read it from here.
166 ///
167 /// The default implementation forwards to [`draw_layers`](Self::draw_layers), which every
168 /// backend implements. [`crate::terminal::Terminal::present`] never calls this method directly (it
169 /// always goes through `draw_layers`, pre-flattened onto layer 0 for a backend that doesn't
170 /// composite; see [`composites_layers`](Self::composites_layers)), so overriding this is
171 /// only worthwhile if a backend has a cheaper direct path for the known-single-layer case
172 /// than its own `draw_layers` would take.
173 ///
174 /// # Errors
175 ///
176 /// See [`draw_layers`](Self::draw_layers), which this forwards to by default and shares
177 /// its error contract with.
178 fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
179 where
180 I: Iterator<Item = DrawCell<'a>>,
181 {
182 self.draw_layers(content)
183 }
184
185 /// Draw changed cells across all layers.
186 ///
187 /// [`crate::terminal::Terminal::present`] always calls this method, never [`draw`](Self::draw)
188 /// directly, for every backend. A backend that renders one glyph per cell and returns
189 /// `false` from [`composites_layers`](Self::composites_layers) (the default) receives a
190 /// stream `present` has already pre-flattened onto layer 0 (all allocated layers
191 /// composited into one frame first, so layers 1+ still appear on every backend, not only
192 /// pixel ones); implementing this is no different from what implementing single-layer
193 /// `draw` used to mean. A pixel/GPU backend that returns `true` from `composites_layers`
194 /// receives the real, multi-layer stream here and does its own compositing (per-pixel or
195 /// per-quad, plus sub-cell offsets and transparency as needed).
196 ///
197 /// When [`needs_full_frame`](Self::needs_full_frame) returns `true`, this
198 /// receives **all** cells from every allocated layer, and the backend
199 /// should clear its output surface before drawing.
200 ///
201 /// That promise holds only together with `composites_layers() == true`:
202 /// [`crate::terminal::Terminal::present`] only reads `needs_full_frame` inside its `composites_layers`
203 /// branch, so a backend
204 /// returning `true` here with the default (`false`) `composites_layers` never actually
205 /// receives a full frame, despite this doc's unconditional wording (retroglyph#763). No
206 /// backend in this workspace uses that combination; a future one that does should either
207 /// also return `true` from `composites_layers`, or treat `needs_full_frame` as dead until
208 /// `Terminal::present`'s dispatch is widened to honor it outside that branch too.
209 ///
210 /// Items are the same [`DrawCell`](crate::backend::DrawCell) [`draw`](Self::draw) receives, read through
211 /// [`DrawCell::layer`](crate::backend::DrawCell::layer) rather than a separate element.
212 ///
213 /// # Errors
214 ///
215 /// `Self::Error` is implementation-defined (a broken pipe or closed terminal for a
216 /// process-backed display, a lost surface for a windowed one); implementations do not
217 /// roll back cells already written before the failure. Because
218 /// [`crate::terminal::Terminal::present`] only swaps its diff buffers into `previous` after the
219 /// call that reached this method succeeds, a failed draw leaves the same cells marked
220 /// dirty, so they are resent on the next successful present rather than silently
221 /// dropped.
222 fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
223 where
224 I: Iterator<Item = DrawCell<'a>>;
225
226 /// Returns `true` if the backend needs the **entire** frame (all cells on
227 /// all layers) on every call to [`draw_layers`](Self::draw_layers), rather
228 /// than just the changed cells.
229 ///
230 /// Pixel-based backends (e.g. `SoftwareRenderer`) need this because
231 /// sub-cell offsets can spill glyph pixels into adjacent cells: without
232 /// a full redraw, orphaned pixels from the previous frame linger.
233 ///
234 /// Only takes effect alongside [`composites_layers`](Self::composites_layers) returning
235 /// `true`: see [`draw_layers`](Self::draw_layers)'s docs for why a `true` here paired with
236 /// the default `composites_layers` does nothing.
237 ///
238 /// The default implementation returns `false`.
239 fn needs_full_frame(&self) -> bool {
240 false
241 }
242
243 /// Whether this backend composites layers itself (per pixel or quad),
244 /// receiving the raw layered stream from [`draw_layers`](Self::draw_layers).
245 ///
246 /// Backends that render one glyph per cell return `false` (the default) and
247 /// receive a pre-flattened, single-layer stream: [`crate::terminal::Terminal::present`]
248 /// composites all allocated layers into one frame first. This makes layers
249 /// 1+ appear on every backend, not only pixel backends. Pixel/GPU backends
250 /// return `true` and composite the layers themselves.
251 fn composites_layers(&self) -> bool {
252 false
253 }
254
255 /// Flush buffered output to the display.
256 ///
257 /// # Errors
258 ///
259 /// `Self::Error` is implementation-defined (a broken pipe, a closed terminal, a lost
260 /// surface). [`crate::terminal::Terminal::present`] calls this only after
261 /// [`draw`](Self::draw)/[`draw_layers`](Self::draw_layers) succeed, and swaps its diff
262 /// buffers only after `flush` also succeeds; a failed flush therefore leaves the
263 /// current frame's cells buffered but unconfirmed, and they are resent on the next
264 /// successful present.
265 fn flush(&mut self) -> Result<(), Self::Error>;
266
267 /// Return current display dimensions.
268 #[must_use]
269 fn size(&self) -> Size;
270
271 /// Clear the entire display.
272 ///
273 /// # Errors
274 ///
275 /// `Self::Error` is implementation-defined (a broken pipe, a closed terminal, a lost
276 /// surface). Unlike [`draw`](Self::draw)/[`flush`](Self::flush), this is not part of
277 /// [`crate::terminal::Terminal::present`]'s per-frame path; callers that invoke it directly
278 /// (some backends also call it internally on resize) should treat a failure as leaving
279 /// the display in an unknown, possibly partially cleared state and retry or tear down
280 /// rather than assume the previous contents are still intact.
281 fn clear(&mut self) -> Result<(), Self::Error>;
282
283 /// Notify the backend of a resize to `size`, updating what [`size`](Self::size) reports.
284 ///
285 /// Called automatically by [`crate::terminal::Terminal::resize`] after both grids are resized.
286 /// Backends that maintain internal state tied to terminal dimensions (such as
287 /// [`Headless`](crate::backend::Headless)) should override this to update that state. The default
288 /// implementation is a no-op.
289 ///
290 /// A driver may also call this directly, ahead of and independent from
291 /// [`crate::terminal::Terminal::resize`], to keep [`size`](Self::size) in sync with an underlying
292 /// surface the moment it changes (a windowed backend reacting to an OS resize, for
293 /// example) without waiting for the app to resize the terminal's grid content in
294 /// response. Doing so does not resize the grid; only [`crate::terminal::Terminal::resize`] does that.
295 fn resize(&mut self, size: Size) {
296 let _ = size;
297 }
298}
299
300/// Polls for and accepts input events.
301///
302/// Backends that never receive events from outside their own [`poll_event`](Self::poll_event)
303/// implementation (e.g. `Crossterm`, which reads its own event stream) can use the default
304/// no-op [`push_event`](Self::push_event) via an empty `impl Input for X {}`.
305///
306/// # Examples
307///
308/// ```
309/// use core::time::Duration;
310/// use retroglyph_core::backend::Input;
311/// use retroglyph_core::event::Event;
312/// use std::collections::VecDeque;
313///
314/// struct QueuedInput(VecDeque<Event>);
315///
316/// impl Input for QueuedInput {
317/// fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
318/// self.0.pop_front()
319/// }
320///
321/// fn push_event(&mut self, event: Event) {
322/// self.0.push_back(event);
323/// }
324/// }
325/// ```
326pub trait Input {
327 /// Poll for an input event, waiting up to `timeout`.
328 fn poll_event(&mut self, timeout: Duration) -> Option<Event>;
329
330 /// Push an event into the backend's event buffer.
331 ///
332 /// Backends that receive events externally (e.g., from a window event
333 /// loop or a test harness) override this to queue events for
334 /// [`poll_event`](Self::poll_event). The default is a no-op.
335 ///
336 /// - Windowed backends: called by `ApplicationHandler` on each event.
337 /// - Headless: called by tests to inject synthetic events.
338 /// - Crossterm: reads from its own event stream; no-op here.
339 fn push_event(&mut self, _event: Event) {}
340}
341
342/// Shows, hides, and moves a text cursor.
343///
344/// Both methods default to a no-op so backends with no text cursor to manage (pixel/windowed
345/// backends, where games draw their own cursor if they want one) can use an empty
346/// `impl Cursor for X {}` instead of writing dead stub bodies by hand.
347///
348/// # Examples
349///
350/// ```
351/// use retroglyph_core::backend::Cursor;
352/// use retroglyph_core::grid::Pos;
353///
354/// struct TrackedCursor {
355/// visible: bool,
356/// position: Pos,
357/// }
358///
359/// impl Cursor for TrackedCursor {
360/// fn set_cursor_visible(&mut self, visible: bool) {
361/// self.visible = visible;
362/// }
363///
364/// fn set_cursor_position(&mut self, position: Pos) {
365/// self.position = position;
366/// }
367/// }
368/// ```
369pub trait Cursor {
370 /// Show or hide the cursor.
371 fn set_cursor_visible(&mut self, _visible: bool) {}
372
373 /// Move the cursor to a position.
374 fn set_cursor_position(&mut self, _position: Pos) {}
375
376 /// Set the cursor's shape (and blink behavior).
377 ///
378 /// Defaults to a no-op, matching [`set_cursor_visible`](Self::set_cursor_visible)/
379 /// [`set_cursor_position`](Self::set_cursor_position): backends with no text cursor to
380 /// manage, or that render on a terminal emulator with no shape-changing escape sequence,
381 /// can ignore this via the default `impl Cursor for X {}`.
382 fn set_cursor_style(&mut self, _style: CursorStyle) {}
383}
384
385/// The text cursor's visual shape and blink behavior.
386///
387/// Mirrors the six shapes a DEC-compatible terminal's `DECSCUSR` escape (`CSI Ps SP q`)
388/// supports: block, underline, and bar, each either blinking or steady. `#[non_exhaustive]`
389/// leaves room for a future shape (e.g. a hollow/outline block) without a breaking change.
390#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
391#[non_exhaustive]
392pub enum CursorStyle {
393 /// A blinking solid block (`█`). The default terminal cursor shape on most emulators.
394 #[default]
395 BlinkingBlock,
396 /// A steady (non-blinking) solid block.
397 SteadyBlock,
398 /// A blinking underscore (`_`).
399 BlinkingUnderline,
400 /// A steady (non-blinking) underscore.
401 SteadyUnderline,
402 /// A blinking vertical bar (`|`), as commonly used for text-insertion cursors.
403 BlinkingBar,
404 /// A steady (non-blinking) vertical bar.
405 SteadyBar,
406}
407
408/// A rendering backend that presents grid content to a display and provides input events.
409///
410/// This is a pure ergonomic bundle over [`Output`](crate::backend::Output), [`Input`](crate::backend::Input), and [`Cursor`](crate::backend::Cursor), with no members
411/// of its own: every type implementing all three gets `Backend` for free, and every generic
412/// call site that only needs one or two facets should bound on those directly instead of
413/// requiring all three through this trait.
414///
415/// # Examples
416///
417/// There is nothing to implement directly: a type gets `Backend` for free the moment it
418/// implements all three facet traits.
419///
420/// ```
421/// use core::time::Duration;
422/// use retroglyph_core::backend::{Backend, Cursor, DrawCell, Input, Output};
423/// use retroglyph_core::event::Event;
424/// use retroglyph_core::grid::Size;
425///
426/// struct NullBackend;
427///
428/// impl Output for NullBackend {
429/// type Error = core::convert::Infallible;
430///
431/// fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
432/// where
433/// I: Iterator<Item = DrawCell<'a>>,
434/// {
435/// Ok(())
436/// }
437///
438/// fn flush(&mut self) -> Result<(), Self::Error> {
439/// Ok(())
440/// }
441///
442/// fn size(&self) -> Size {
443/// Size::new(1, 1)
444/// }
445///
446/// fn clear(&mut self) -> Result<(), Self::Error> {
447/// Ok(())
448/// }
449/// }
450///
451/// impl Input for NullBackend {
452/// fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
453/// None
454/// }
455/// }
456///
457/// impl Cursor for NullBackend {}
458///
459/// fn assert_is_backend<B: Backend>(_backend: &B) {}
460/// assert_is_backend(&NullBackend);
461/// ```
462pub trait Backend: Output + Input + Cursor {}
463
464impl<T: Output + Input + Cursor> Backend for T {}