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}