retroglyph_window/backend.rs
1//! [`WindowBackend`]: the generic [`Backend`](retroglyph_core::Backend)
2//! implementation for windowed presenters.
3
4use crate::presenter::Presenter;
5use retroglyph_core::backend::Backend;
6use retroglyph_core::event::Event;
7use retroglyph_core::grid::{Pos, Size};
8use retroglyph_core::tile::Tile;
9use std::collections::VecDeque;
10use std::time::Duration;
11
12/// A [`Backend`] built from a [`Presenter`] plus an input event queue.
13///
14/// `Backend` fuses input and output, which does not fit a window: some event
15/// loop owns input, while a per-renderer surface owns output. `WindowBackend`
16/// reunites the two so [`Terminal`](retroglyph_core::Terminal) gets the full
17/// `Backend` it needs, while renderer crates implement only [`Presenter`]:
18///
19/// ```text
20/// event loop.push_event(e) ──> VecDeque<Event> ──> app.poll_event()
21/// │
22/// v
23/// Terminal<WindowBackend<P>>
24/// │
25/// draw / flush / resize v
26/// ◄──────────────────── WindowBackend
27/// │
28/// v
29/// P: Presenter (output)
30/// ```
31///
32/// With the `winit` feature enabled, `winit::run_windowed` and
33/// `winit::run_app` own the event loop, call `push_event` as winit events
34/// are translated, and call [`Presenter::present`] once per frame; callers
35/// never touch `WindowBackend` directly. With `winit` disabled,
36/// `retroglyph-window` exports no event loop at all: a caller driving its
37/// own loop (SDL2, tao, a custom driver) constructs
38/// `WindowBackend::new(presenter)` itself, calls `push_event` for each
39/// translated input event, and calls `Terminal::present` (which drives
40/// `Presenter::flush`) plus `presenter_mut().present()` once per frame.
41///
42/// # Example: driving without `winit`
43///
44/// ```rust
45/// use retroglyph_core::{Backend, Event, Pos, Size, Terminal, Tile};
46/// use retroglyph_window::{Presenter, WindowBackend, WindowHandle};
47/// use std::sync::Arc;
48/// use std::time::Duration;
49///
50/// struct NullPresenter;
51///
52/// impl Presenter for NullPresenter {
53/// type Error = core::convert::Infallible;
54/// type SurfaceError = core::convert::Infallible;
55///
56/// fn draw<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
57/// where
58/// I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>,
59/// {
60/// Ok(())
61/// }
62///
63/// fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
64/// where
65/// I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
66/// {
67/// Ok(())
68/// }
69///
70/// fn flush(&mut self) -> Result<(), Self::Error> {
71/// Ok(())
72/// }
73///
74/// fn size(&self) -> Size {
75/// Size { width: 4, height: 2 }
76/// }
77///
78/// fn clear(&mut self) -> Result<(), Self::Error> {
79/// Ok(())
80/// }
81///
82/// fn resize(&mut self, _size: Size) {}
83///
84/// fn init_surface(&mut self, _window: Arc<dyn WindowHandle>) -> Result<(), Self::SurfaceError> {
85/// Ok(())
86/// }
87///
88/// fn resize_surface(&mut self, _width: u32, _height: u32) {}
89///
90/// fn present(&mut self) -> Result<(), Self::SurfaceError> {
91/// Ok(())
92/// }
93///
94/// fn cell_size(&self) -> (u32, u32) {
95/// (8, 16)
96/// }
97/// }
98///
99/// // A caller driving its own loop (SDL2, tao, a hand-rolled driver) builds
100/// // `WindowBackend` directly -- no `winit` feature required.
101/// let backend = WindowBackend::new(NullPresenter);
102/// let mut term = Terminal::new(backend);
103///
104/// // The loop pushes each translated input event onto the queue...
105/// term.backend_mut().push_event(Event::FocusGained);
106///
107/// // ...and the app drains it through the normal `Terminal` polling API,
108/// // which never blocks for `WindowBackend`.
109/// while term.poll(Duration::ZERO).is_some() {}
110///
111/// // Once per frame: `Terminal::present` diffs the grid and drives
112/// // `Presenter::flush`, then the caller drives `Presenter::present` itself
113/// // to push pixels to the window.
114/// term.present().unwrap();
115/// term.backend_mut().presenter_mut().present().unwrap();
116/// ```
117///
118/// [`poll_event`](Backend::poll_event) never blocks: frame timing is owned by
119/// the event loop, not by input waits.
120pub struct WindowBackend<P: Presenter> {
121 presenter: P,
122 events: VecDeque<Event>,
123}
124
125impl<P: Presenter> WindowBackend<P> {
126 /// Wrap a presenter, creating an empty event queue.
127 #[must_use]
128 pub const fn new(presenter: P) -> Self {
129 Self {
130 presenter,
131 events: VecDeque::new(),
132 }
133 }
134
135 /// The wrapped presenter.
136 #[must_use]
137 pub const fn presenter(&self) -> &P {
138 &self.presenter
139 }
140
141 /// The wrapped presenter, mutably.
142 pub const fn presenter_mut(&mut self) -> &mut P {
143 &mut self.presenter
144 }
145
146 /// Unwrap into the presenter, discarding queued events.
147 #[must_use]
148 pub fn into_presenter(self) -> P {
149 self.presenter
150 }
151}
152
153impl<P: Presenter> Backend for WindowBackend<P> {
154 type Error = P::Error;
155
156 fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
157 where
158 I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>,
159 {
160 self.presenter.draw(content)
161 }
162
163 fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
164 where
165 I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
166 {
167 self.presenter.draw_layers(content)
168 }
169
170 fn flush(&mut self) -> Result<(), Self::Error> {
171 self.presenter.flush()
172 }
173
174 fn size(&self) -> Size {
175 self.presenter.size()
176 }
177
178 fn clear(&mut self) -> Result<(), Self::Error> {
179 self.presenter.clear()
180 }
181
182 fn resize(&mut self, size: Size) {
183 self.presenter.resize(size);
184 }
185
186 fn needs_full_frame(&self) -> bool {
187 self.presenter.needs_full_frame()
188 }
189
190 fn composites_layers(&self) -> bool {
191 self.presenter.composites_layers()
192 }
193
194 fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
195 // Non-blocking by design: the caller's event loop drives frame
196 // timing, so there is nothing to sleep on here.
197 self.events.pop_front()
198 }
199
200 fn push_event(&mut self, event: Event) {
201 self.events.push_back(event);
202 }
203
204 fn set_cursor_visible(&mut self, _visible: bool) {
205 // No hardware text cursor in windowed mode; games draw their own.
206 }
207
208 fn set_cursor_position(&mut self, _position: Pos) {
209 // No hardware text cursor in windowed mode.
210 }
211}