Skip to main content

retroglyph_core/backend/
mod.rs

1//! Pluggable rendering backends.
2//!
3//! Only the [`Backend`] trait and the dependency-free [`Headless`] test
4//! backend live here. Platform backends (crossterm, software/winit) are
5//! separate crates (`retroglyph-crossterm`, `retroglyph-software`) that
6//! depend on this one and implement [`Backend`].
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/// A rendering backend that presents grid content to a display
29/// and provides input events.
30pub trait Backend {
31    /// Error type returned by fallible operations.
32    type Error: BackendError;
33
34    /// Draw changed cells to the output surface.
35    ///
36    /// The third element of each item is the tile's full grapheme cluster
37    /// (see [`Grid::grapheme`](crate::grid::Grid::grapheme)), `Some` only for
38    /// multi-codepoint EGCs (combining marks, ZWJ sequences); `None` means
39    /// render the tile's [`glyph`](Tile::glyph) alone. `Tile` itself never
40    /// carries this text (it lives in a side-table on `Grid`), so backends
41    /// that need the full grapheme at draw time must read it from here
42    /// rather than from the tile.
43    ///
44    /// # Errors
45    ///
46    /// Returns an error if the backend cannot write to the output surface
47    /// (e.g., a broken pipe or closed terminal).
48    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
49    where
50        I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>;
51
52    /// Draw changed cells across all layers.
53    ///
54    /// The default implementation forwards layer-0 tiles to [`draw`](Self::draw)
55    /// and ignores higher layers. Override this to support multi-layer
56    /// compositing, sub-cell offsets, or transparency.
57    ///
58    /// When [`needs_full_frame`](Self::needs_full_frame) returns `true`, this
59    /// receives **all** cells from every allocated layer, and the backend
60    /// should clear its output surface before drawing.
61    ///
62    /// See [`draw`](Self::draw) for the meaning of each item's grapheme text.
63    ///
64    /// # Errors
65    ///
66    /// See [`draw`](Self::draw).
67    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
68    where
69        I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
70    {
71        self.draw(content.filter_map(|(layer, pos, tile, extra)| {
72            if layer == 0 {
73                Some((pos, tile, extra))
74            } else {
75                None
76            }
77        }))
78    }
79
80    /// Returns `true` if the backend needs the **entire** frame (all cells on
81    /// all layers) on every call to [`draw_layers`](Self::draw_layers), rather
82    /// than just the changed cells.
83    ///
84    /// Pixel-based backends (e.g. `SoftwareRenderer`) need this because
85    /// sub-cell offsets can spill glyph pixels into adjacent cells — without
86    /// a full redraw, orphaned pixels from the previous frame linger.
87    ///
88    /// The default implementation returns `false`.
89    fn needs_full_frame(&self) -> bool {
90        false
91    }
92
93    /// Whether this backend composites layers itself (per pixel or quad),
94    /// receiving the raw layered stream from [`draw_layers`](Self::draw_layers).
95    ///
96    /// Backends that render one glyph per cell return `false` (the default) and
97    /// receive a pre-flattened, single-layer stream: [`crate::Terminal::present`]
98    /// composites all allocated layers into one frame first. This makes layers
99    /// 1+ appear on every backend, not only pixel backends. Pixel/GPU backends
100    /// return `true` and composite the layers themselves.
101    fn composites_layers(&self) -> bool {
102        false
103    }
104
105    /// Flush buffered output to the display.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error if the backend cannot flush (e.g., a broken pipe).
110    fn flush(&mut self) -> Result<(), Self::Error>;
111
112    /// Return current display dimensions.
113    #[must_use]
114    fn size(&self) -> Size;
115
116    /// Clear the entire display.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the backend cannot clear the display.
121    fn clear(&mut self) -> Result<(), Self::Error>;
122
123    /// Notify the backend of a terminal resize.
124    ///
125    /// Called automatically by [`crate::Terminal::resize`] after both grids are resized.
126    /// Backends that maintain internal state tied to terminal dimensions (such as
127    /// [`Headless`]) should override this to update that state. The default
128    /// implementation is a no-op.
129    fn resize(&mut self, size: Size) {
130        let _ = size;
131    }
132
133    /// Poll for an input event, waiting up to `timeout`.
134    fn poll_event(&mut self, timeout: Duration) -> Option<Event>;
135
136    /// Show or hide the cursor.
137    fn set_cursor_visible(&mut self, visible: bool);
138
139    /// Move the cursor to a position.
140    fn set_cursor_position(&mut self, position: Pos);
141
142    /// Push an event into the backend's event buffer.
143    ///
144    /// Backends that receive events externally (e.g., from a window event
145    /// loop or a test harness) override this to queue events for
146    /// [`poll_event`](Self::poll_event). The default is a no-op.
147    ///
148    /// - Windowed backends: called by `ApplicationHandler` on each event.
149    /// - Headless: called by tests to inject synthetic events.
150    /// - Crossterm: reads from its own event stream; no-op here.
151    fn push_event(&mut self, _event: Event) {}
152}