Skip to main content

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}