Skip to main content

retroglyph_window/
backend.rs

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