Skip to main content

retroglyph_window/
backend.rs

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