Skip to main content

retroglyph_core/backend/
mod.rs

1//! Pluggable rendering backends.
2//!
3//! The [`Output`], [`Input`], and [`Cursor`] traits (plus the [`Backend`] bundle that ties them
4//! together) and the dependency-free [`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::event::Event;
13use crate::grid::{Pos, Size};
14use crate::tile::Tile;
15use core::time::Duration;
16
17/// Associated error type used by all fallible backend methods.
18///
19/// Backends that are infallible (e.g. `Headless`, `SoftwareRenderer`) use
20/// [`core::convert::Infallible`]. Fallible backends (e.g. `Crossterm`) use
21/// [`std::io::Error`].
22pub trait BackendError: core::fmt::Display + core::fmt::Debug {}
23
24impl BackendError for core::convert::Infallible {}
25#[cfg(feature = "std")]
26impl BackendError for std::io::Error {}
27
28/// Draws grid content to a display and reports its dimensions.
29///
30/// This is the only one of the three backend facets ([`Output`], [`Input`], [`Cursor`]) that's
31/// fallible: writing to a real display can fail (a broken pipe, a closed terminal, a lost
32/// surface), so every mutating method here returns `Result<(), Self::Error>`.
33pub trait Output {
34    /// Error type returned by fallible operations.
35    type Error: BackendError;
36
37    /// Draw changed cells to the output surface.
38    ///
39    /// The third element of each item is the tile's full grapheme cluster
40    /// (see [`Grid::grapheme`](crate::grid::Grid::grapheme)), `Some` only for
41    /// multi-codepoint EGCs (combining marks, ZWJ sequences); `None` means
42    /// render the tile's [`glyph`](Tile::glyph) alone. `Tile` itself never
43    /// carries this text (it lives in a side-table on `Grid`), so backends
44    /// that need the full grapheme at draw time must read it from here
45    /// rather than from the tile.
46    ///
47    /// **This field is only ever `Some` when the crate's `egc` feature is enabled.** Without
48    /// `egc`, `Grid` never populates its EGC side-table, so every item's third element is
49    /// always `None` -- there is no multi-codepoint grapheme text to read, ever. Backends that
50    /// don't opt into `egc` support can safely ignore this field entirely (e.g. `let _ = extra;`
51    /// and render each tile from [`glyph`](Tile::glyph) alone); it carries no information in
52    /// that configuration.
53    ///
54    /// # Errors
55    ///
56    /// Returns an error if the backend cannot write to the output surface
57    /// (e.g., a broken pipe or closed terminal).
58    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
59    where
60        I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>;
61
62    /// Draw changed cells across all layers.
63    ///
64    /// The default implementation forwards layer-0 tiles to [`draw`](Self::draw)
65    /// and ignores higher layers. Override this to support multi-layer
66    /// compositing, sub-cell offsets, or transparency.
67    ///
68    /// When [`needs_full_frame`](Self::needs_full_frame) returns `true`, this
69    /// receives **all** cells from every allocated layer, and the backend
70    /// should clear its output surface before drawing.
71    ///
72    /// See [`draw`](Self::draw) for the meaning of each item's grapheme text, including the
73    /// `egc`-feature-only contract for the trailing `Option<&str>`.
74    ///
75    /// # Errors
76    ///
77    /// See [`draw`](Self::draw).
78    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
79    where
80        I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
81    {
82        self.draw(content.filter_map(|(layer, pos, tile, extra)| {
83            if layer == 0 {
84                Some((pos, tile, extra))
85            } else {
86                None
87            }
88        }))
89    }
90
91    /// Returns `true` if the backend needs the **entire** frame (all cells on
92    /// all layers) on every call to [`draw_layers`](Self::draw_layers), rather
93    /// than just the changed cells.
94    ///
95    /// Pixel-based backends (e.g. `SoftwareRenderer`) need this because
96    /// sub-cell offsets can spill glyph pixels into adjacent cells — without
97    /// a full redraw, orphaned pixels from the previous frame linger.
98    ///
99    /// The default implementation returns `false`.
100    fn needs_full_frame(&self) -> bool {
101        false
102    }
103
104    /// Whether this backend composites layers itself (per pixel or quad),
105    /// receiving the raw layered stream from [`draw_layers`](Self::draw_layers).
106    ///
107    /// Backends that render one glyph per cell return `false` (the default) and
108    /// receive a pre-flattened, single-layer stream: [`crate::Terminal::present`]
109    /// composites all allocated layers into one frame first. This makes layers
110    /// 1+ appear on every backend, not only pixel backends. Pixel/GPU backends
111    /// return `true` and composite the layers themselves.
112    fn composites_layers(&self) -> bool {
113        false
114    }
115
116    /// Flush buffered output to the display.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the backend cannot flush (e.g., a broken pipe).
121    fn flush(&mut self) -> Result<(), Self::Error>;
122
123    /// Return current display dimensions.
124    #[must_use]
125    fn size(&self) -> Size;
126
127    /// Clear the entire display.
128    ///
129    /// # Errors
130    ///
131    /// Returns an error if the backend cannot clear the display.
132    fn clear(&mut self) -> Result<(), Self::Error>;
133
134    /// Notify the backend of a terminal resize.
135    ///
136    /// Called automatically by [`crate::Terminal::resize`] after both grids are resized.
137    /// Backends that maintain internal state tied to terminal dimensions (such as
138    /// [`Headless`]) should override this to update that state. The default
139    /// implementation is a no-op.
140    fn resize(&mut self, size: Size) {
141        let _ = size;
142    }
143}
144
145/// Polls for and accepts input events.
146///
147/// Backends that never receive events from outside their own [`poll_event`](Self::poll_event)
148/// implementation (e.g. `Crossterm`, which reads its own event stream) can use the default
149/// no-op [`push_event`](Self::push_event) via an empty `impl Input for X {}`.
150pub trait Input {
151    /// Poll for an input event, waiting up to `timeout`.
152    fn poll_event(&mut self, timeout: Duration) -> Option<Event>;
153
154    /// Push an event into the backend's event buffer.
155    ///
156    /// Backends that receive events externally (e.g., from a window event
157    /// loop or a test harness) override this to queue events for
158    /// [`poll_event`](Self::poll_event). The default is a no-op.
159    ///
160    /// - Windowed backends: called by `ApplicationHandler` on each event.
161    /// - Headless: called by tests to inject synthetic events.
162    /// - Crossterm: reads from its own event stream; no-op here.
163    fn push_event(&mut self, _event: Event) {}
164}
165
166/// Shows, hides, and moves a text cursor.
167///
168/// Both methods default to a no-op so backends with no text cursor to manage (pixel/windowed
169/// backends, where games draw their own cursor if they want one) can use an empty
170/// `impl Cursor for X {}` instead of writing dead stub bodies by hand.
171pub trait Cursor {
172    /// Show or hide the cursor.
173    fn set_cursor_visible(&mut self, _visible: bool) {}
174
175    /// Move the cursor to a position.
176    fn set_cursor_position(&mut self, _position: Pos) {}
177}
178
179/// A rendering backend that presents grid content to a display and provides input events.
180///
181/// This is a pure ergonomic bundle over [`Output`], [`Input`], and [`Cursor`], with no members
182/// of its own: every type implementing all three gets `Backend` for free, and every generic
183/// call site that only needs one or two facets should bound on those directly instead of
184/// requiring all three through this trait.
185pub trait Backend: Output + Input + Cursor {}
186
187impl<T: Output + Input + Cursor> Backend for T {}