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::{Cursor, Input, Output};
6use retroglyph_core::event::{Event, MouseEvent, MouseEventKind};
7use retroglyph_core::grid::{Pos, Size};
8use retroglyph_core::tile::Tile;
9use std::collections::VecDeque;
10use std::time::Duration;
11
12/// A [`Backend`](retroglyph_core::Backend) built from a [`Presenter`] plus an input event queue.
13///
14/// [`Input`] and [`Output`] are independent facets of `Backend`, which does not fit a window as
15/// one type: some event loop owns input, while a per-renderer surface owns output.
16/// `WindowBackend` reunites the two -- implementing `Output` by delegating to `P`, `Input` via
17/// its own event queue, and the no-op default `Cursor` -- so [`Terminal`](retroglyph_core::Terminal)
18/// gets the full `Backend` it needs, while renderer crates implement only [`Presenter`]:
19///
20/// ```text
21/// event loop.push_event(e) ──> VecDeque<Event> ──> app.poll_event()
22/// │
23/// v
24/// Terminal<WindowBackend<P>>
25/// │
26/// draw / flush / resize v
27/// ◄──────────────────── WindowBackend
28/// │
29/// v
30/// P: Presenter (output)
31/// ```
32///
33/// With the `winit` feature enabled, `winit::run_windowed` and
34/// `winit::run_app` own the event loop, call `push_event` as winit events
35/// are translated, and call [`Presenter::present`] once per frame; callers
36/// never touch `WindowBackend` directly. With `winit` disabled,
37/// `retroglyph-window` exports no event loop at all: a caller driving its
38/// own loop (SDL2, tao, a custom driver) constructs
39/// `WindowBackend::new(presenter)` itself, calls `push_event` for each
40/// translated input event, and calls `Terminal::present` (which drives
41/// `Presenter::flush`) plus `presenter_mut().present()` once per frame.
42///
43/// # Example: driving without `winit`
44///
45/// ```rust
46/// use retroglyph_core::{Backend, Event, Input, Output, Pos, Size, Terminal, Tile};
47/// use retroglyph_window::{Presenter, WindowBackend, WindowHandle};
48/// use std::sync::Arc;
49/// use std::time::Duration;
50///
51/// struct NullPresenter;
52///
53/// impl Output for NullPresenter {
54/// type Error = 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///
85/// impl Presenter for NullPresenter {
86/// type SurfaceError = core::convert::Infallible;
87///
88/// fn init_surface(&mut self, _window: Arc<dyn WindowHandle>) -> Result<(), Self::SurfaceError> {
89/// Ok(())
90/// }
91///
92/// fn resize_surface(&mut self, _width: u32, _height: u32) {}
93///
94/// fn present(&mut self) -> Result<(), Self::SurfaceError> {
95/// Ok(())
96/// }
97///
98/// fn cell_size(&self) -> (u32, u32) {
99/// (8, 16)
100/// }
101/// }
102///
103/// // A caller driving its own loop (SDL2, tao, a hand-rolled driver) builds
104/// // `WindowBackend` directly -- no `winit` feature required.
105/// let backend = WindowBackend::new(NullPresenter);
106/// let mut term = Terminal::new(backend);
107///
108/// // The loop pushes each translated input event onto the queue...
109/// term.backend_mut().push_event(Event::FocusGained);
110///
111/// // ...and the app drains it through the normal `Terminal` polling API,
112/// // which never blocks for `WindowBackend`.
113/// while term.poll(Duration::ZERO).is_some() {}
114///
115/// // Once per frame: `Terminal::present` diffs the grid and drives
116/// // `Presenter::flush`, then the caller drives `Presenter::present` itself
117/// // to push pixels to the window.
118/// term.present().unwrap();
119/// term.backend_mut().presenter_mut().present().unwrap();
120/// ```
121///
122/// [`poll_event`](Input::poll_event) never blocks: frame timing is owned by
123/// the event loop, not by input waits.
124pub struct WindowBackend<P: Presenter> {
125 presenter: P,
126 events: VecDeque<Event>,
127}
128
129impl<P: Presenter> WindowBackend<P> {
130 /// Wrap a presenter, creating an empty event queue.
131 #[must_use]
132 pub const fn new(presenter: P) -> Self {
133 Self {
134 presenter,
135 events: VecDeque::new(),
136 }
137 }
138
139 /// The wrapped presenter.
140 #[must_use]
141 pub const fn presenter(&self) -> &P {
142 &self.presenter
143 }
144
145 /// The wrapped presenter, mutably.
146 pub const fn presenter_mut(&mut self) -> &mut P {
147 &mut self.presenter
148 }
149
150 /// Unwrap into the presenter, discarding queued events.
151 #[must_use]
152 pub fn into_presenter(self) -> P {
153 self.presenter
154 }
155}
156
157impl<P: Presenter> Output for WindowBackend<P> {
158 type Error = P::Error;
159
160 fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
161 where
162 I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>,
163 {
164 self.presenter.draw(content)
165 }
166
167 fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
168 where
169 I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
170 {
171 self.presenter.draw_layers(content)
172 }
173
174 fn flush(&mut self) -> Result<(), Self::Error> {
175 self.presenter.flush()
176 }
177
178 fn size(&self) -> Size {
179 self.presenter.size()
180 }
181
182 fn clear(&mut self) -> Result<(), Self::Error> {
183 self.presenter.clear()
184 }
185
186 fn resize(&mut self, size: Size) {
187 self.presenter.resize(size);
188 }
189
190 fn needs_full_frame(&self) -> bool {
191 self.presenter.needs_full_frame()
192 }
193
194 fn composites_layers(&self) -> bool {
195 self.presenter.composites_layers()
196 }
197}
198
199impl<P: Presenter> Input for WindowBackend<P> {
200 fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
201 // Non-blocking by design: the caller's event loop drives frame
202 // timing, so there is nothing to sleep on here.
203 self.events.pop_front()
204 }
205
206 fn push_event(&mut self, event: Event) {
207 // Coalesce consecutive `Mouse(Moved)` events: winit can deliver `CursorMoved` at device
208 // polling rate (hundreds/sec) though only the latest position matters once the next frame
209 // polls the queue, so replace the queue's tail in place instead of growing it unbounded
210 // (retroglyph#294). Every other event kind (clicks, scrolls, keys, resize, ...) still
211 // pushes in O(1) as before; only two back-to-back `Moved` events collapse.
212 if let Event::Mouse(MouseEvent {
213 kind: MouseEventKind::Moved,
214 ..
215 }) = &event
216 && let Some(
217 back @ Event::Mouse(MouseEvent {
218 kind: MouseEventKind::Moved,
219 ..
220 }),
221 ) = self.events.back_mut()
222 {
223 *back = event;
224 return;
225 }
226 self.events.push_back(event);
227 }
228}
229
230// No hardware text cursor in windowed mode (games draw their own): the trait's no-op default
231// bodies are exactly right here.
232impl<P: Presenter> Cursor for WindowBackend<P> {}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::presenter::WindowHandle;
238 use retroglyph_core::event::{KeyModifiers, MouseButton};
239 use std::sync::Arc;
240
241 struct NullPresenter;
242
243 impl Output for NullPresenter {
244 type Error = core::convert::Infallible;
245
246 fn draw<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
247 where
248 I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>,
249 {
250 Ok(())
251 }
252
253 fn draw_layers<'a, I>(&mut self, _content: I) -> Result<(), Self::Error>
254 where
255 I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
256 {
257 Ok(())
258 }
259
260 fn flush(&mut self) -> Result<(), Self::Error> {
261 Ok(())
262 }
263
264 fn size(&self) -> Size {
265 Size {
266 width: 4,
267 height: 2,
268 }
269 }
270
271 fn clear(&mut self) -> Result<(), Self::Error> {
272 Ok(())
273 }
274
275 fn resize(&mut self, _size: Size) {}
276 }
277
278 impl Presenter for NullPresenter {
279 type SurfaceError = core::convert::Infallible;
280
281 fn init_surface(
282 &mut self,
283 _window: Arc<dyn WindowHandle>,
284 ) -> Result<(), Self::SurfaceError> {
285 Ok(())
286 }
287
288 fn resize_surface(&mut self, _width: u32, _height: u32) {}
289
290 fn present(&mut self) -> Result<(), Self::SurfaceError> {
291 Ok(())
292 }
293
294 fn cell_size(&self) -> (u32, u32) {
295 (8, 16)
296 }
297 }
298
299 fn moved(x: u16) -> Event {
300 Event::Mouse(MouseEvent {
301 kind: MouseEventKind::Moved,
302 position: Pos { x, y: 0 },
303 pixel_position: None,
304 modifiers: KeyModifiers::NONE,
305 })
306 }
307
308 /// Regression test for retroglyph#294: a burst of consecutive `Moved` events must coalesce
309 /// down to the single most recent one instead of growing the queue by one entry per event.
310 #[test]
311 fn consecutive_moved_events_coalesce_to_one() {
312 let mut backend = WindowBackend::new(NullPresenter);
313 for x in 0..1_000u16 {
314 backend.push_event(moved(x));
315 }
316 assert_eq!(backend.events.len(), 1);
317 assert_eq!(backend.poll_event(Duration::ZERO), Some(moved(999)));
318 assert_eq!(backend.poll_event(Duration::ZERO), None);
319 }
320
321 /// A non-`Moved` event between two `Moved` bursts must not be swallowed: only *consecutive*
322 /// `Moved` events collapse, so interleaving a click still yields three distinct events.
323 #[test]
324 fn non_moved_event_breaks_coalescing() {
325 let mut backend = WindowBackend::new(NullPresenter);
326 backend.push_event(moved(1));
327 backend.push_event(moved(2));
328 backend.push_event(Event::Mouse(MouseEvent {
329 kind: MouseEventKind::Down(MouseButton::Left),
330 position: Pos { x: 2, y: 0 },
331 pixel_position: None,
332 modifiers: KeyModifiers::NONE,
333 }));
334 backend.push_event(moved(3));
335 assert_eq!(backend.events.len(), 3);
336 assert_eq!(backend.poll_event(Duration::ZERO), Some(moved(2)));
337 assert!(matches!(
338 backend.poll_event(Duration::ZERO),
339 Some(Event::Mouse(MouseEvent {
340 kind: MouseEventKind::Down(MouseButton::Left),
341 ..
342 }))
343 ));
344 assert_eq!(backend.poll_event(Duration::ZERO), Some(moved(3)));
345 }
346}