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