Skip to main content

retroglyph_window/
backend.rs

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