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/// # Example: driving without `winit`
43///
44/// ```rust
45/// use retroglyph_core::{Backend, Event, Pos, Size, Terminal, Tile};
46/// use retroglyph_window::{Presenter, WindowBackend, WindowHandle};
47/// use std::sync::Arc;
48/// use std::time::Duration;
49///
50/// struct NullPresenter;
51///
52/// impl Presenter for NullPresenter {
53///     type Error = core::convert::Infallible;
54///     type SurfaceError = 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///     fn init_surface(&mut self, _window: Arc<dyn WindowHandle>) -> Result<(), Self::SurfaceError> {
85///         Ok(())
86///     }
87///
88///     fn resize_surface(&mut self, _width: u32, _height: u32) {}
89///
90///     fn present(&mut self) -> Result<(), Self::SurfaceError> {
91///         Ok(())
92///     }
93///
94///     fn cell_size(&self) -> (u32, u32) {
95///         (8, 16)
96///     }
97/// }
98///
99/// // A caller driving its own loop (SDL2, tao, a hand-rolled driver) builds
100/// // `WindowBackend` directly -- no `winit` feature required.
101/// let backend = WindowBackend::new(NullPresenter);
102/// let mut term = Terminal::new(backend);
103///
104/// // The loop pushes each translated input event onto the queue...
105/// term.backend_mut().push_event(Event::FocusGained);
106///
107/// // ...and the app drains it through the normal `Terminal` polling API,
108/// // which never blocks for `WindowBackend`.
109/// while term.poll(Duration::ZERO).is_some() {}
110///
111/// // Once per frame: `Terminal::present` diffs the grid and drives
112/// // `Presenter::flush`, then the caller drives `Presenter::present` itself
113/// // to push pixels to the window.
114/// term.present().unwrap();
115/// term.backend_mut().presenter_mut().present().unwrap();
116/// ```
117///
118/// [`poll_event`](Backend::poll_event) never blocks: frame timing is owned by
119/// the event loop, not by input waits.
120pub struct WindowBackend<P: Presenter> {
121    presenter: P,
122    events: VecDeque<Event>,
123}
124
125impl<P: Presenter> WindowBackend<P> {
126    /// Wrap a presenter, creating an empty event queue.
127    #[must_use]
128    pub const fn new(presenter: P) -> Self {
129        Self {
130            presenter,
131            events: VecDeque::new(),
132        }
133    }
134
135    /// The wrapped presenter.
136    #[must_use]
137    pub const fn presenter(&self) -> &P {
138        &self.presenter
139    }
140
141    /// The wrapped presenter, mutably.
142    pub const fn presenter_mut(&mut self) -> &mut P {
143        &mut self.presenter
144    }
145
146    /// Unwrap into the presenter, discarding queued events.
147    #[must_use]
148    pub fn into_presenter(self) -> P {
149        self.presenter
150    }
151}
152
153impl<P: Presenter> Backend for WindowBackend<P> {
154    type Error = P::Error;
155
156    fn draw<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
157    where
158        I: Iterator<Item = (Pos, &'a Tile, Option<&'a str>)>,
159    {
160        self.presenter.draw(content)
161    }
162
163    fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
164    where
165        I: Iterator<Item = (u8, Pos, &'a Tile, Option<&'a str>)>,
166    {
167        self.presenter.draw_layers(content)
168    }
169
170    fn flush(&mut self) -> Result<(), Self::Error> {
171        self.presenter.flush()
172    }
173
174    fn size(&self) -> Size {
175        self.presenter.size()
176    }
177
178    fn clear(&mut self) -> Result<(), Self::Error> {
179        self.presenter.clear()
180    }
181
182    fn resize(&mut self, size: Size) {
183        self.presenter.resize(size);
184    }
185
186    fn needs_full_frame(&self) -> bool {
187        self.presenter.needs_full_frame()
188    }
189
190    fn composites_layers(&self) -> bool {
191        self.presenter.composites_layers()
192    }
193
194    fn poll_event(&mut self, _timeout: Duration) -> Option<Event> {
195        // Non-blocking by design: the caller's event loop drives frame
196        // timing, so there is nothing to sleep on here.
197        self.events.pop_front()
198    }
199
200    fn push_event(&mut self, event: Event) {
201        self.events.push_back(event);
202    }
203
204    fn set_cursor_visible(&mut self, _visible: bool) {
205        // No hardware text cursor in windowed mode; games draw their own.
206    }
207
208    fn set_cursor_position(&mut self, _position: Pos) {
209        // No hardware text cursor in windowed mode.
210    }
211}