retroglyph/backend/mod.rs
1//! Pluggable rendering backends.
2
3#[cfg(feature = "crossterm")]
4pub mod crossterm;
5pub mod headless;
6#[cfg(feature = "software")]
7pub mod software;
8
9#[cfg(feature = "crossterm")]
10pub use crossterm::Crossterm;
11pub use headless::Headless;
12#[cfg(feature = "software")]
13pub use software::{SoftwareBackend, SoftwareRenderer, WindowedBackend};
14
15use crate::event::Event;
16use crate::grid::{Pos, Size};
17use crate::tile::Tile;
18use core::time::Duration;
19
20/// A rendering backend that presents grid content to a display
21/// and provides input events.
22pub trait Backend {
23 /// Draw changed cells to the output surface.
24 fn draw<'a, I>(&mut self, content: I)
25 where
26 I: Iterator<Item = (Pos, &'a Tile)>;
27
28 /// Draw changed cells across all layers.
29 ///
30 /// The default implementation forwards layer-0 tiles to [`draw`](Self::draw)
31 /// and ignores higher layers. Override this to support multi-layer
32 /// compositing, sub-cell offsets, or transparency.
33 ///
34 /// When [`needs_full_frame`](Self::needs_full_frame) returns `true`, this
35 /// receives **all** cells from every allocated layer, and the backend
36 /// should clear its output surface before drawing.
37 fn draw_layers<'a, I>(&mut self, content: I)
38 where
39 I: Iterator<Item = (u8, Pos, &'a Tile)>,
40 {
41 self.draw(content.filter_map(
42 |(layer, pos, tile)| {
43 if layer == 0 { Some((pos, tile)) } else { None }
44 },
45 ));
46 }
47
48 /// Returns `true` if the backend needs the **entire** frame (all cells on
49 /// all layers) on every call to [`draw_layers`](Self::draw_layers), rather
50 /// than just the changed cells.
51 ///
52 /// Pixel-based backends (e.g. `SoftwareRenderer`) need this because
53 /// sub-cell offsets can spill glyph pixels into adjacent cells — without
54 /// a full redraw, orphaned pixels from the previous frame linger.
55 ///
56 /// The default implementation returns `false`.
57 fn needs_full_frame(&self) -> bool {
58 false
59 }
60
61 /// Flush buffered output to the display.
62 fn flush(&mut self);
63
64 /// Return current display dimensions.
65 #[must_use]
66 fn size(&self) -> Size;
67
68 /// Clear the entire display.
69 fn clear(&mut self);
70
71 /// Notify the backend of a terminal resize.
72 ///
73 /// Called automatically by [`crate::Terminal::resize`] after both grids are resized.
74 /// Backends that maintain internal state tied to terminal dimensions (such as
75 /// [`Headless`]) should override this to update that state. The default
76 /// implementation is a no-op.
77 fn resize(&mut self, size: Size) {
78 let _ = size;
79 }
80
81 /// Poll for an input event, waiting up to `timeout`.
82 fn poll_event(&mut self, timeout: Duration) -> Option<Event>;
83
84 /// Returns `false` if the backend has been disconnected from its
85 /// output (e.g. the window was closed). The game loop should
86 /// terminate when this returns `false`.
87 ///
88 /// The default implementation always returns `true`. Override for
89 /// backends that can detect disconnect.
90 fn is_connected(&self) -> bool {
91 true
92 }
93
94 /// Show or hide the cursor.
95 fn set_cursor_visible(&mut self, visible: bool);
96
97 /// Move the cursor to a position.
98 fn set_cursor_position(&mut self, position: Pos);
99
100 /// Push an event into the backend's event buffer.
101 ///
102 /// Backends that receive events externally (e.g., from a window event
103 /// loop or a test harness) override this to queue events for
104 /// [`poll_event`](Self::poll_event). The default is a no-op.
105 ///
106 /// - Windowed backends: called by `ApplicationHandler` on each event.
107 /// - Headless: called by tests to inject synthetic events.
108 /// - Crossterm: reads from its own event stream; no-op here.
109 fn push_event(&mut self, _event: Event) {}
110}