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::Backend;
6use retroglyph_core::event::Event;
7use retroglyph_core::grid::{Pos, Size};
8use retroglyph_core::tile::Tile;
9use std::collections::VecDeque;
10use std::time::Duration;
11
12/// A [`Backend`] built from a [`Presenter`] plus an input event queue.
13///
14/// `Backend` fuses input and output, which does not fit a window: some event
15/// loop owns input, while a per-renderer surface owns output. `WindowBackend`
16/// reunites the two so [`Terminal`](retroglyph_core::Terminal) gets the full
17/// `Backend` it needs, while renderer crates implement only [`Presenter`]:
18///
19/// ```text
20/// event loop.push_event(e) ──> VecDeque<Event> ──> app.poll_event()
21///                                                        │
22///                                                        v
23///                                             Terminal<WindowBackend<P>>
24///                                                        │
25///                              draw / flush / resize     v
26///                              ◄────────────────────  WindowBackend
27///                                                        │
28///                                                        v
29///                                                 P: Presenter (output)
30/// ```
31///
32/// With the `winit` feature enabled, `winit::run_windowed` and
33/// `winit::run_app` own the event loop, call `push_event` as winit events
34/// are translated, and call [`Presenter::present`] once per frame; callers
35/// never touch `WindowBackend` directly. With `winit` disabled,
36/// `retroglyph-window` exports no event loop at all: a caller driving its
37/// own loop (SDL2, tao, a custom driver) constructs
38/// `WindowBackend::new(presenter)` itself, calls `push_event` for each
39/// translated input event, and calls `Terminal::present` (which drives
40/// `Presenter::flush`) plus `presenter_mut().present()` once per frame.
41///
42/// [`poll_event`](Backend::poll_event) never blocks: frame timing is owned by
43/// the event loop, not by input waits.
44pub struct WindowBackend<P: Presenter> {
45    presenter: P,
46    events: VecDeque<Event>,
47}
48
49impl<P: Presenter> WindowBackend<P> {
50    /// Wrap a presenter, creating an empty event queue.
51    #[must_use]
52    pub const fn new(presenter: P) -> Self {
53        Self {
54            presenter,
55            events: VecDeque::new(),
56        }
57    }
58
59    /// The wrapped presenter.
60    #[must_use]
61    pub const fn presenter(&self) -> &P {
62        &self.presenter
63    }
64
65    /// The wrapped presenter, mutably.
66    pub const fn presenter_mut(&mut self) -> &mut P {
67        &mut self.presenter
68    }
69
70    /// Unwrap into the presenter, discarding queued events.
71    #[must_use]
72    pub fn into_presenter(self) -> P {
73        self.presenter
74    }
75}
76
77impl<P: Presenter> Backend for WindowBackend<P> {
78    type Error = P::Error;
79
80    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
81    where
82        I: Iterator<Item = (Pos, &'a Tile)>,
83    {
84        self.presenter.draw(content)
85    }
86
87    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
88    where
89        I: Iterator<Item = (u8, Pos, &'a Tile)>,
90    {
91        self.presenter.draw_layers(content)
92    }
93
94    fn flush(&mut self) -> Result<(), Self::Error> {
95        self.presenter.flush()
96    }
97
98    fn size(&self) -> Size {
99        self.presenter.size()
100    }
101
102    fn clear(&mut self) -> Result<(), Self::Error> {
103        self.presenter.clear()
104    }
105
106    fn resize(&mut self, size: Size) {
107        self.presenter.resize(size);
108    }
109
110    fn needs_full_frame(&self) -> bool {
111        self.presenter.needs_full_frame()
112    }
113
114    fn composites_layers(&self) -> bool {
115        self.presenter.composites_layers()
116    }
117
118    fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
119        // Non-blocking by design: the caller's event loop drives frame
120        // timing, so there is nothing to sleep on here.
121        self.events.pop_front()
122    }
123
124    fn push_event(&mut self, event: Event) {
125        self.events.push_back(event);
126    }
127
128    fn set_cursor_visible(&mut self, _visible: bool) {
129        // No hardware text cursor in windowed mode; games draw their own.
130    }
131
132    fn set_cursor_position(&mut self, _position: Pos) {
133        // No hardware text cursor in windowed mode.
134    }
135}