Skip to main content

retroglyph_window/
presenter.rs

1//! The [`Presenter`] trait: what a renderer crate implements to rasterize a
2//! grid and present it to a window surface.
3//!
4//! `Presenter` is the output half of [`Backend`](retroglyph_core::Backend)
5//! plus window-surface operations, with no input methods: the event loop
6//! owns input, and [`WindowBackend`](crate::WindowBackend) forwards
7//! translated events into its own queue instead.
8//!
9//! | Presenter | `present()` | `init_surface()` |
10//! |---|---|---|
11//! | `SoftwareRenderer` (retroglyph-software) | Copies pixel buffer to softbuffer surface | Creates `softbuffer::Context` + `Surface` |
12//! | `WgpuRenderer` (future) | Submits render pass + presents swap chain | Creates `wgpu::Surface` + `Device` |
13//! | `GlRenderer` (future) | Draws full-screen quad + swaps buffers | Creates GL context from the window |
14//!
15//! See the crate-level docs (`crate` root, "DPI, scale, and the resize contract" and
16//! "Threading model" sections) for the physical-pixel/no-auto-scaling contract on
17//! [`cell_size`](Presenter::cell_size), the sub-cell-remainder behavior on
18//! [`resize_surface`](Presenter::resize_surface), and the single-threaded execution model
19//! every `Presenter` implementation runs under.
20
21use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
22use retroglyph_core::backend::BackendError;
23use retroglyph_core::grid::{Pos, Size};
24use retroglyph_core::tile::Tile;
25use std::sync::Arc;
26
27/// A window/display handle pair, as one trait.
28///
29/// Presenters receive [`raw-window-handle`](raw_window_handle) types, not a
30/// concrete `winit::window::Window`: softbuffer, wgpu, and glutin all accept
31/// these handles directly, so any windowing library that produces them can
32/// drive the same presenter, and only this crate depends on winit itself.
33///
34/// `raw-window-handle` has no combined trait, and surface libraries need to
35/// *own* the handle (softbuffer stores it for the surface's lifetime), so
36/// presenters receive `Arc<dyn WindowHandle>` -- rwh implements the handle
37/// traits for `Arc<H: ?Sized>`, so the trait object passes straight into
38/// `softbuffer::Surface::new` / `wgpu::Instance::create_surface`.
39pub trait WindowHandle: HasWindowHandle + HasDisplayHandle {}
40
41impl<T: HasWindowHandle + HasDisplayHandle + ?Sized> WindowHandle for T {}
42
43/// A renderer that rasterizes grid content and presents it to a window
44/// surface.
45///
46/// Mirrors the output half of [`Backend`](retroglyph_core::Backend) (`draw`,
47/// `draw_layers`, `flush`, `size`, `clear`, `resize`) so
48/// [`WindowBackend`](crate::WindowBackend) can delegate those methods
49/// wholesale, and adds the surface lifecycle (`init_surface`,
50/// `resize_surface`, `present`, `cell_size`) that the event loop drives.
51///
52/// The `needs_full_frame` and `composites_layers` defaults are `true`: every
53/// windowed presenter is a pixel-family backend that composites layers
54/// itself, receiving the raw per-layer stream instead of a pre-flattened
55/// single layer. Only character-cell terminal backends return `false`, and
56/// those implement [`Backend`](retroglyph_core::Backend) directly instead of
57/// this trait.
58pub trait Presenter {
59    /// Rasterization error (mirrors `Backend::Error`).
60    ///
61    /// In-memory rasterizers are infallible and use
62    /// [`core::convert::Infallible`].
63    type Error: BackendError;
64
65    /// Surface lifecycle error (context creation, buffer acquisition,
66    /// present).
67    type SurfaceError: core::fmt::Debug + core::fmt::Display;
68
69    /// Rasterize changed cells (single layer).
70    ///
71    /// # Errors
72    ///
73    /// Returns [`Self::Error`] if rasterization fails.
74    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
75    where
76        I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>;
77
78    /// Rasterize the full layered frame.
79    ///
80    /// Because [`needs_full_frame`](Self::needs_full_frame) defaults to
81    /// `true`, this receives every cell of every allocated layer and should
82    /// clear its target before drawing.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`Self::Error`] if rasterization fails.
87    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
88    where
89        I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>;
90
91    /// Flush buffered rasterization work.
92    ///
93    /// Distinct from [`present`](Self::present): `flush` completes drawing
94    /// into the presenter's own target; `present` pushes that target to the
95    /// OS window.
96    ///
97    /// # Errors
98    ///
99    /// Returns [`Self::Error`] if the flush fails.
100    fn flush(&mut self) -> Result<(), Self::Error>;
101
102    /// Current grid dimensions in cells.
103    #[must_use]
104    fn size(&self) -> Size;
105
106    /// Clear the rasterization target.
107    ///
108    /// # Errors
109    ///
110    /// Returns [`Self::Error`] if the clear fails.
111    fn clear(&mut self) -> Result<(), Self::Error>;
112
113    /// Resize the grid (in cells), reallocating the rasterization target.
114    fn resize(&mut self, size: Size);
115
116    /// Whether the full frame is required on every `draw_layers` call.
117    ///
118    /// Defaults to `true` for the windowed family (sub-cell offsets spill
119    /// pixels across cells; partial redraws would leave orphans).
120    #[must_use]
121    fn needs_full_frame(&self) -> bool {
122        true
123    }
124
125    /// Whether this presenter composites layers itself, receiving the raw
126    /// `(layer, Pos, Tile)` stream instead of a pre-flattened single layer.
127    ///
128    /// Defaults to `true` for the windowed family.
129    #[must_use]
130    fn composites_layers(&self) -> bool {
131        true
132    }
133
134    /// Initialize the window surface.
135    ///
136    /// Called once from the loop's `resumed` handler. The presenter creates
137    /// its platform surface (softbuffer surface, wgpu device+surface, GL
138    /// context) from the raw window/display handles.
139    ///
140    /// # Errors
141    ///
142    /// Returns [`Self::SurfaceError`] if surface or context creation fails.
143    fn init_surface(&mut self, window: Arc<dyn WindowHandle>) -> Result<(), Self::SurfaceError>;
144
145    /// Resize the window surface to a new physical pixel size.
146    ///
147    /// Called on every window resize event with `width`/`height` already resolved by the
148    /// caller -- for the `winit` driver (see `winit::run::WindowApp::resize_to`), that means
149    /// `cols * cell_w` x `rows * cell_h`, where `cols`/`rows` are the window's physical size
150    /// divided down to whole cells. Any sub-cell remainder is truncated, not centered or
151    /// cleared: when the window's physical size isn't an exact multiple of the cell size,
152    /// `width`/`height` here are the largest whole-cell-multiple that fits, which can be
153    /// smaller than the window's actual physical size. The OS window itself is never resized
154    /// to compensate, so a non-exact-multiple resize leaves a thin strip at the window's
155    /// trailing edge outside the surface -- retroglyph does not paint or clear that strip;
156    /// whatever the OS/windowing backend leaves there remains visible until a subsequent
157    /// resize covers it.
158    fn resize_surface(&mut self, width: u32, height: u32);
159
160    /// Notify the presenter that the window's scale factor (DPI) changed.
161    ///
162    /// Called when the window moves to a display with a different pixel density, or the
163    /// system DPI setting changes. The event loop follows this with
164    /// [`resize_surface`](Self::resize_surface) for the window's new physical size, so
165    /// this hook only needs to handle DPI-dependent state that isn't a plain buffer
166    /// resize (e.g. regenerating a font atlas rasterized for a particular scale).
167    ///
168    /// Defaults to a no-op: presenters whose rasterization doesn't depend on DPI (like
169    /// `SoftwareRenderer`'s integer `scale` config, set once at construction) need no
170    /// action here.
171    fn scale_factor_changed(&mut self, _scale_factor: f64) {}
172
173    /// Present the rasterized frame to the window surface.
174    ///
175    /// Called after each app tick. A lost frame is not fatal; the caller
176    /// logs the error and continues.
177    ///
178    /// # Errors
179    ///
180    /// Returns [`Self::SurfaceError`] if the surface buffer can't be acquired
181    /// or presented (e.g. context lost on wasm, page flip pending on
182    /// DRI/KMS).
183    fn present(&mut self) -> Result<(), Self::SurfaceError>;
184
185    /// Cell size in physical pixels `(width, height)`.
186    ///
187    /// Physical pixels, not logical/DPI-scaled pixels, and never auto-scaled by this crate for
188    /// display DPI -- see the crate-level "DPI, scale, and the resize contract" docs. A presenter
189    /// whose cells should grow on a `HiDPI` display must change what this returns itself (from
190    /// [`resize`](Self::resize) or [`scale_factor_changed`](Self::scale_factor_changed)); absent
191    /// that, it stays constant for the presenter's lifetime.
192    ///
193    /// `(u32, u32)` rather than [`Size`] because grid coordinates are `u16`
194    /// but pixel arithmetic uses `u32` (winit `PhysicalSize`).
195    #[must_use]
196    fn cell_size(&self) -> (u32, u32);
197}