Skip to main content

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/// How a backend wants layers handed to [`Output::draw_layers`].
120///
121/// Replaces the independent `needs_full_frame`/`composites_layers` booleans this crate used
122/// before retroglyph#1278: those could express a combination no backend used and
123/// [`crate::terminal::Terminal::present`] never honored (`needs_full_frame() == true` paired
124/// with the default `composites_layers() == false`). Nesting `needs_full_frame` inside
125/// `PixelLayered` makes that combination unrepresentable instead of merely undocumented.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
127#[non_exhaustive]
128pub enum Compositing {
129    /// Layers are flattened onto layer 0 before they reach the backend; one cell, one draw.
130    ///
131    /// [`crate::terminal::Terminal::present`] composites all allocated layers into one frame
132    /// itself, so layers 1+ still appear on every backend, not only pixel ones, and the backend
133    /// receives only the changed cells.
134    CellFlattened,
135    /// The backend composites layers itself (per pixel or quad), receiving the raw layered
136    /// stream from [`Output::draw_layers`].
137    PixelLayered {
138        /// If `true`, every [`Output::draw_layers`] call receives **all** cells from every
139        /// allocated layer, not just the changed ones, and the backend should clear its output
140        /// surface before drawing. Pixel-based backends need this because sub-cell offsets can
141        /// spill glyph pixels into adjacent cells: without a full redraw, orphaned pixels from
142        /// the previous frame linger.
143        needs_full_frame: bool,
144    },
145}
146
147/// Draws grid content to a display and reports its dimensions.
148///
149/// This is the only one of the three backend facets ([`Output`](crate::backend::Output), [`Input`](crate::backend::Input), [`Cursor`](crate::backend::Cursor)) that's
150/// fallible: writing to a real display can fail (a broken pipe, a closed terminal, a lost
151/// surface), so every mutating method here returns `Result<(), Self::Error>`.
152///
153/// # Examples
154///
155/// ```
156/// use retroglyph_core::backend::{DrawCell, Output};
157/// use retroglyph_core::grid::Size;
158///
159/// struct NullOutput;
160///
161/// impl Output for NullOutput {
162///     type Error = core::convert::Infallible;
163///
164///     fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
165///     where
166///         I: Iterator<Item = DrawCell<'a>>,
167///     {
168///         Ok(())
169///     }
170///
171///     fn flush(&mut self) -> Result<(), Self::Error> {
172///         Ok(())
173///     }
174///
175///     fn size(&self) -> Size {
176///         Size::new(4, 2)
177///     }
178///
179///     fn clear(&mut self) -> Result<(), Self::Error> {
180///         Ok(())
181///     }
182/// }
183/// ```
184pub trait Output {
185    /// Error type returned by fallible operations.
186    type Error: BackendError;
187
188    /// Draw changed cells to the output surface, layer 0 only.
189    ///
190    /// Every cell arrives as a [`DrawCell`](crate::backend::DrawCell), which carries the out-of-line state a [`Tile`](crate::tile::Tile)
191    /// cannot: its full grapheme cluster and its tint. Both live in a side table on
192    /// [`Grid`](crate::grid::Grid)
193    /// rather than in the tile, so a backend that needs either must read it from here.
194    ///
195    /// The default implementation forwards to [`draw_layers`](Self::draw_layers), which every
196    /// backend implements. [`crate::terminal::Terminal::present`] never calls this method directly (it
197    /// always goes through `draw_layers`, pre-flattened onto layer 0 for a backend that doesn't
198    /// composite; see [`compositing`](Self::compositing)), so overriding this is
199    /// only worthwhile if a backend has a cheaper direct path for the known-single-layer case
200    /// than its own `draw_layers` would take.
201    ///
202    /// # Errors
203    ///
204    /// See [`draw_layers`](Self::draw_layers), which this forwards to by default and shares
205    /// its error contract with.
206    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
207    where
208        I: Iterator<Item = DrawCell<'a>>,
209    {
210        self.draw_layers(content)
211    }
212
213    /// Draw changed cells across all layers.
214    ///
215    /// [`crate::terminal::Terminal::present`] always calls this method, never [`draw`](Self::draw)
216    /// directly, for every backend. A backend that returns [`Compositing::CellFlattened`]
217    /// (the default, from [`compositing`](Self::compositing)) receives a stream `present` has
218    /// already pre-flattened onto layer 0 (all allocated layers composited into one frame first,
219    /// so layers 1+ still appear on every backend, not only pixel ones); implementing this is no
220    /// different from what implementing single-layer `draw` used to mean. A pixel/GPU backend
221    /// that returns [`Compositing::PixelLayered`] receives the real,
222    /// multi-layer stream here and does its own compositing (per-pixel or per-quad, plus
223    /// sub-cell offsets and transparency as needed).
224    ///
225    /// When [`compositing`](Self::compositing) returns `PixelLayered { needs_full_frame: true }`,
226    /// this receives **all** cells from every allocated layer, and the backend should clear its
227    /// output surface before drawing; `PixelLayered { needs_full_frame: false }` and
228    /// `CellFlattened` both receive only the changed cells.
229    ///
230    /// Items are the same [`DrawCell`](crate::backend::DrawCell) [`draw`](Self::draw) receives, read through
231    /// [`DrawCell::layer`](crate::backend::DrawCell::layer) rather than a separate element.
232    ///
233    /// # Errors
234    ///
235    /// `Self::Error` is implementation-defined (a broken pipe or closed terminal for a
236    /// process-backed display, a lost surface for a windowed one); implementations do not
237    /// roll back cells already written before the failure. Because
238    /// [`crate::terminal::Terminal::present`] only swaps its diff buffers into `previous` after the
239    /// call that reached this method succeeds, a failed draw leaves the same cells marked
240    /// dirty, so they are resent on the next successful present rather than silently
241    /// dropped.
242    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
243    where
244        I: Iterator<Item = DrawCell<'a>>;
245
246    /// How this backend wants layers handed to [`draw_layers`](Self::draw_layers).
247    ///
248    /// Backends that render one glyph per cell return [`Compositing::CellFlattened`]
249    /// (the default) and receive a pre-flattened, single-layer stream:
250    /// [`crate::terminal::Terminal::present`] composites all allocated layers into one frame
251    /// first. This makes layers 1+ appear on every backend, not only pixel backends. Pixel/GPU
252    /// backends return [`Compositing::PixelLayered`] and composite the
253    /// layers themselves; its `needs_full_frame` field asks for **all** cells on every
254    /// [`draw_layers`](Self::draw_layers) call (all cells on all layers) rather than just the
255    /// changed ones, because pixel-based backends (e.g. `SoftwareRenderer`) can spill glyph
256    /// pixels from sub-cell offsets into adjacent cells: without a full redraw, orphaned pixels
257    /// from the previous frame linger.
258    fn compositing(&self) -> Compositing {
259        Compositing::CellFlattened
260    }
261
262    /// Flush buffered output to the display.
263    ///
264    /// # Errors
265    ///
266    /// `Self::Error` is implementation-defined (a broken pipe, a closed terminal, a lost
267    /// surface). [`crate::terminal::Terminal::present`] calls this only after
268    /// [`draw`](Self::draw)/[`draw_layers`](Self::draw_layers) succeed, and swaps its diff
269    /// buffers only after `flush` also succeeds; a failed flush therefore leaves the
270    /// current frame's cells buffered but unconfirmed, and they are resent on the next
271    /// successful present.
272    fn flush(&mut self) -> Result<(), Self::Error>;
273
274    /// Return current display dimensions.
275    #[must_use]
276    fn size(&self) -> Size;
277
278    /// Clear the entire display.
279    ///
280    /// # Errors
281    ///
282    /// `Self::Error` is implementation-defined (a broken pipe, a closed terminal, a lost
283    /// surface). Unlike [`draw`](Self::draw)/[`flush`](Self::flush), this is not part of
284    /// [`crate::terminal::Terminal::present`]'s per-frame path; callers that invoke it directly
285    /// (some backends also call it internally on resize) should treat a failure as leaving
286    /// the display in an unknown, possibly partially cleared state and retry or tear down
287    /// rather than assume the previous contents are still intact.
288    fn clear(&mut self) -> Result<(), Self::Error>;
289
290    /// Notify the backend of a resize to `size`, updating what [`size`](Self::size) reports.
291    ///
292    /// Called automatically by [`crate::terminal::Terminal::resize`] after both grids are resized.
293    /// Backends that maintain internal state tied to terminal dimensions (such as
294    /// [`Headless`](crate::backend::Headless)) should override this to update that state. The default
295    /// implementation is a no-op.
296    ///
297    /// A driver may also call this directly, ahead of and independent from
298    /// [`crate::terminal::Terminal::resize`], to keep [`size`](Self::size) in sync with an underlying
299    /// surface the moment it changes (a windowed backend reacting to an OS resize, for
300    /// example) without waiting for the app to resize the terminal's grid content in
301    /// response. Doing so does not resize the grid; only [`crate::terminal::Terminal::resize`] does that.
302    fn resize(&mut self, size: Size) {
303        let _ = size;
304    }
305}
306
307/// Polls for and accepts input events.
308///
309/// Backends that never receive events from outside their own [`poll_event`](Self::poll_event)
310/// implementation (e.g. `Crossterm`, which reads its own event stream) can use the default
311/// no-op [`push_event`](Self::push_event) via an empty `impl Input for X {}`.
312///
313/// # Examples
314///
315/// ```
316/// use core::time::Duration;
317/// use retroglyph_core::backend::Input;
318/// use retroglyph_core::event::Event;
319/// use std::collections::VecDeque;
320///
321/// struct QueuedInput(VecDeque<Event>);
322///
323/// impl Input for QueuedInput {
324///     fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
325///         self.0.pop_front()
326///     }
327///
328///     fn push_event(&mut self, event: Event) {
329///         self.0.push_back(event);
330///     }
331/// }
332/// ```
333pub trait Input {
334    /// Poll for an input event, waiting up to `timeout`.
335    fn poll_event(&mut self, timeout: Duration) -> Option<Event>;
336
337    /// Push an event into the backend's event buffer.
338    ///
339    /// Backends that receive events externally (e.g., from a window event
340    /// loop or a test harness) override this to queue events for
341    /// [`poll_event`](Self::poll_event). The default is a no-op.
342    ///
343    /// - Windowed backends: called by `ApplicationHandler` on each event.
344    /// - Headless: called by tests to inject synthetic events.
345    /// - Crossterm: also queues events pushed here (a test harness injecting synthetic input, or
346    ///   a driver handing back an event it already read off the real terminal); its own
347    ///   `poll_event` still reads the real terminal's stream separately.
348    ///
349    /// Coalescing a burst of consecutive `Event::Mouse(MouseEventKind::Moved)` pushes down to the
350    /// latest one is not required of every implementation, only of backends whose events can
351    /// arrive at device-polling rate from an external source (a windowed backend's motion events,
352    /// for example); see `retroglyph_core::testing::conformance::assert_input_contract` for the
353    /// harness that pins it, and that module's doc for which backends it binds.
354    fn push_event(&mut self, _event: Event) {}
355}
356
357/// Shows, hides, and moves a text cursor.
358///
359/// Both methods default to a no-op so backends with no text cursor to manage (pixel/windowed
360/// backends, where games draw their own cursor if they want one) can use an empty
361/// `impl Cursor for X {}` instead of writing dead stub bodies by hand.
362///
363/// # Examples
364///
365/// ```
366/// use retroglyph_core::backend::Cursor;
367/// use retroglyph_core::grid::Pos;
368///
369/// struct TrackedCursor {
370///     visible: bool,
371///     position: Pos,
372/// }
373///
374/// impl Cursor for TrackedCursor {
375///     fn set_cursor_visible(&mut self, visible: bool) {
376///         self.visible = visible;
377///     }
378///
379///     fn set_cursor_position(&mut self, position: Pos) {
380///         self.position = position;
381///     }
382/// }
383/// ```
384pub trait Cursor {
385    /// Show or hide the cursor.
386    fn set_cursor_visible(&mut self, _visible: bool) {}
387
388    /// Move the cursor to a position.
389    fn set_cursor_position(&mut self, _position: Pos) {}
390
391    /// Set the cursor's shape (and blink behavior).
392    ///
393    /// Defaults to a no-op, matching [`set_cursor_visible`](Self::set_cursor_visible)/
394    /// [`set_cursor_position`](Self::set_cursor_position): backends with no text cursor to
395    /// manage, or that render on a terminal emulator with no shape-changing escape sequence,
396    /// can ignore this via the default `impl Cursor for X {}`.
397    fn set_cursor_style(&mut self, _style: CursorStyle) {}
398}
399
400/// The text cursor's visual shape and blink behavior.
401///
402/// Mirrors the six shapes a DEC-compatible terminal's `DECSCUSR` escape (`CSI Ps SP q`)
403/// supports: block, underline, and bar, each either blinking or steady. `#[non_exhaustive]`
404/// leaves room for a future shape (e.g. a hollow/outline block) without a breaking change.
405#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
406#[non_exhaustive]
407pub enum CursorStyle {
408    /// A blinking solid block (`█`). The default terminal cursor shape on most emulators.
409    #[default]
410    BlinkingBlock,
411    /// A steady (non-blinking) solid block.
412    SteadyBlock,
413    /// A blinking underscore (`_`).
414    BlinkingUnderline,
415    /// A steady (non-blinking) underscore.
416    SteadyUnderline,
417    /// A blinking vertical bar (`|`), as commonly used for text-insertion cursors.
418    BlinkingBar,
419    /// A steady (non-blinking) vertical bar.
420    SteadyBar,
421}
422
423/// A rendering backend that presents grid content to a display and provides input events.
424///
425/// This is a pure ergonomic bundle over [`Output`](crate::backend::Output), [`Input`](crate::backend::Input), and [`Cursor`](crate::backend::Cursor), with no members
426/// of its own: every type implementing all three gets `Backend` for free, and every generic
427/// call site that only needs one or two facets should bound on those directly instead of
428/// requiring all three through this trait.
429///
430/// # Examples
431///
432/// There is nothing to implement directly: a type gets `Backend` for free the moment it
433/// implements all three facet traits.
434///
435/// ```
436/// use core::time::Duration;
437/// use retroglyph_core::backend::{Backend, Cursor, DrawCell, Input, Output};
438/// use retroglyph_core::event::Event;
439/// use retroglyph_core::grid::Size;
440///
441/// struct NullBackend;
442///
443/// impl Output for NullBackend {
444///     type Error = core::convert::Infallible;
445///
446///     fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
447///     where
448///         I: Iterator<Item = DrawCell<'a>>,
449///     {
450///         Ok(())
451///     }
452///
453///     fn flush(&mut self) -> Result<(), Self::Error> {
454///         Ok(())
455///     }
456///
457///     fn size(&self) -> Size {
458///         Size::new(1, 1)
459///     }
460///
461///     fn clear(&mut self) -> Result<(), Self::Error> {
462///         Ok(())
463///     }
464/// }
465///
466/// impl Input for NullBackend {
467///     fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
468///         None
469///     }
470/// }
471///
472/// impl Cursor for NullBackend {}
473///
474/// fn assert_is_backend<B: Backend>(_backend: &B) {}
475/// assert_is_backend(&NullBackend);
476/// ```
477pub trait Backend: Output + Input + Cursor {}
478
479impl<T: Output + Input + Cursor> Backend for T {}