retroglyph_core/terminal/input.rs
1//! Input polling: [`poll`](Terminal::poll) and the queue/lookahead helpers built on it.
2//!
3//! Every event enters through [`poll_backend`](Terminal::poll_backend), the single point where a
4//! backend-sourced [`Event::Resize`] gets applied to the grids; [`poll`](Terminal::poll) and the
5//! other methods here all route through it (directly, or via `queued_events`) so a resize is
6//! never applied twice for the same logical event.
7
8use super::Terminal;
9use crate::backend::Backend;
10use crate::event::Event;
11use alloc::vec::Vec;
12use core::time::Duration;
13
14impl<B: Backend> Terminal<B> {
15 /// Polls for an input event, waiting up to `timeout`.
16 ///
17 /// If an event was previously buffered by [`has_input`](Self::has_input), it is
18 /// returned immediately. Otherwise, the backend is polled for a new event.
19 ///
20 /// [`Event::Resize`] events arriving from the backend are automatically applied: both
21 /// grids are resized before the event is returned to the caller, so the game loop can
22 /// immediately redraw at the new size. An event coming back off this terminal's own queue
23 /// (from [`requeue_events`](Self::requeue_events), or buffered by
24 /// [`wait_for_input`](Self::wait_for_input)) was already applied when it first entered, so
25 /// it is returned as-is rather than resized again. See `poll_backend`.
26 pub fn poll(&mut self, timeout: Duration) -> Option<Event> {
27 if let Some(event) = self.queued_events.pop_front() {
28 // Already passed through `poll_backend` (or was requeued from an event that did),
29 // so any `Resize` it carries was applied then; applying it again here would resize
30 // twice for one logical event. See `poll_backend`.
31 return Some(event);
32 }
33 self.poll_backend(timeout)
34 }
35
36 /// Polls the backend directly (bypassing `queued_events`), applying [`Event::Resize`]
37 /// immediately when found.
38 ///
39 /// This is the single point where a freshly-polled event enters the terminal, so it's also
40 /// the only place `resize` should be called for an event on its way in: callers handling an
41 /// event already taken from `queued_events` (via [`poll`](Self::poll)'s queue-pop branch, or
42 /// via [`requeue_events`](Self::requeue_events)) must not call `resize` again for it, or a
43 /// single resize gets applied twice.
44 ///
45 /// Skips the resize when `w, h` already matches [`size`](Self::size): a windowed driver
46 /// (`retroglyph-window`'s winit driver) now resizes the terminal itself before this event is
47 /// ever polled, purely so the app doesn't have to react to it for correctness. Without this
48 /// guard, an app that *does* poll and see that same `Event::Resize` would pay for a second,
49 /// redundant grid resize/clear of a size that was already applied.
50 fn poll_backend(&mut self, timeout: Duration) -> Option<Event> {
51 let event = self.backend.poll_event(timeout)?;
52 if let Event::Resize(w, h) = event
53 && self.size() != crate::grid::Size::new(w, h)
54 {
55 self.resize(w, h);
56 }
57 Some(event)
58 }
59
60 /// Hands `events` back to this terminal's own queue, in order, so a later
61 /// [`poll`](Self::poll)/[`drain_events`](Self::drain_events)/[`drain_events_into`](Self::drain_events_into)
62 /// call yields them again before the backend is polled for anything new.
63 ///
64 /// This is the supported way for a wrapper that drains events to intercept some of them
65 /// (e.g. `retroglyph-ui`' `PerfOverlayApp` filtering out its own toggle key) to give
66 /// the rest back: it goes through `Terminal`'s own queue, never a backend-specific input
67 /// path, so it works identically on every [`Backend`] regardless of how (or whether) that
68 /// backend implements [`Input::push_event`](crate::backend::Input::push_event).
69 pub fn requeue_events(&mut self, events: impl IntoIterator<Item = Event>) {
70 self.queued_events.extend(events);
71 }
72
73 /// Drains all available events without blocking.
74 ///
75 /// Returns an iterator that yields every pending event: the internal queued event
76 /// followed by all events buffered in the backend. The iterator polls the backend
77 /// with zero timeout repeatedly until `None` is returned.
78 ///
79 /// This is needed for frame-based game loops (e.g. software backend + WASM, where
80 /// frames are gated by `requestAnimationFrame`). Multiple keypresses can arrive
81 /// between frames; draining all of them ensures accumulated input doesn't replay in
82 /// slow motion.
83 ///
84 /// Crossterm and headless backends can also use this, but the single-event `poll`
85 /// pattern works for them because their loops aren't frame-capped.
86 ///
87 /// # One-shot semantics
88 ///
89 /// The returned iterator borrows `self` and drains the queue as it is consumed: the
90 /// first caller to iterate it gets every pending event, and a second, independent
91 /// call to `drain_events` afterward gets nothing. If more than one subsystem needs
92 /// this frame's input (e.g. persistent chrome and an active screen), collect once
93 /// and share the collected events by reference, or use
94 /// [`drain_events_into`](Self::drain_events_into) to drain into a reusable buffer
95 /// instead of allocating a fresh `Vec` every frame.
96 pub fn drain_events(&mut self) -> impl Iterator<Item = Event> + use<'_, B> {
97 struct DrainEvents<'a, B: Backend> {
98 terminal: &'a mut Terminal<B>,
99 }
100
101 impl<B: Backend> Iterator for DrainEvents<'_, B> {
102 type Item = Event;
103
104 fn next(&mut self) -> Option<Event> {
105 self.terminal.poll(Duration::ZERO)
106 }
107 }
108
109 impl<B: Backend> core::iter::FusedIterator for DrainEvents<'_, B> {}
110
111 DrainEvents { terminal: self }
112 }
113
114 /// Drains all available events without blocking, appending them to `buf`.
115 ///
116 /// `buf` is cleared first, then filled with every pending event in the same order
117 /// [`drain_events`](Self::drain_events) would yield them. Unlike `drain_events`, the
118 /// borrow of `self` ends when this call returns, so the terminal is free to draw or
119 /// be polled again afterward, and the caller can hand `buf` to multiple consumers by
120 /// shared reference without materializing a new `Vec` every frame.
121 ///
122 /// This is the same shape as `std::io::Read::read_to_end`: allocate the buffer once
123 /// at startup, reuse it every frame, and let this method manage its contents.
124 pub fn drain_events_into(&mut self, buf: &mut Vec<Event>) {
125 buf.clear();
126 while let Some(event) = self.poll(Duration::ZERO) {
127 buf.push(event);
128 }
129 }
130
131 /// Checks if a pending input event is available without blocking.
132 ///
133 /// If an event is already buffered, returns `true`. Otherwise, polls the backend
134 /// with zero timeout. If the backend returns an event, it is stored in the internal
135 /// buffer and `true` is returned; otherwise, returns `false`.
136 pub fn has_input(&mut self) -> bool {
137 self.wait_for_input(Duration::ZERO)
138 }
139
140 /// Blocks until an input event is available or `timeout` elapses, without consuming it.
141 ///
142 /// Like [`has_input`](Self::has_input), a discovered event is buffered internally so a
143 /// subsequent [`poll`](Self::poll), [`has_input`](Self::has_input), or
144 /// [`drain_events`](Self::drain_events) call still observes it: this method only answers
145 /// "did something happen", it never hands the event to the caller. That's what lets a driver
146 /// loop block between frames without stealing the event the app's own `update` reads; see
147 /// [`run_on_with`](crate::app::run_on_with)'s use of this for [`Flow::Idle`](crate::app::Flow::Idle).
148 ///
149 /// Returns `true` if an event arrived within `timeout`, `false` if `timeout` elapsed with
150 /// nothing pending. Pass [`Duration::MAX`] to block indefinitely.
151 ///
152 /// Backends that never block (e.g. [`Headless`](crate::backend::Headless), which returns
153 /// immediately regardless of `timeout`; see [`Input::poll_event`](crate::backend::Input::poll_event))
154 /// return promptly rather than actually waiting; this method is a real wait only on
155 /// backends that genuinely block (crossterm, window).
156 pub fn wait_for_input(&mut self, timeout: Duration) -> bool {
157 if !self.queued_events.is_empty() {
158 return true;
159 }
160 let Some(event) = self.poll_backend(timeout) else {
161 return false;
162 };
163 self.queued_events.push_back(event);
164 true
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::backend::{Cursor, DrawCell, Headless, Input, Output};
172 use crate::grid::{Rect, Size};
173 use crate::terminal::LayerOp;
174
175 #[test]
176 fn test_terminal_poll_and_read() {
177 let backend = Headless::new(10, 10);
178 let mut terminal = Terminal::new(backend);
179
180 assert_eq!(terminal.poll(Duration::ZERO), None);
181
182 terminal.backend_mut().push_event(Event::Close);
183 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Close));
184
185 terminal.backend_mut().push_event(Event::Resize(80, 25));
186 assert_eq!(terminal.poll(Duration::MAX), Some(Event::Resize(80, 25)));
187 }
188
189 #[test]
190 fn test_terminal_has_input() {
191 let backend = Headless::new(10, 10);
192 let mut terminal = Terminal::new(backend);
193
194 assert!(!terminal.has_input());
195
196 terminal.backend_mut().push_event(Event::Close);
197 assert!(terminal.has_input());
198 assert!(terminal.has_input()); // Repeated calls should still be true
199
200 // Read/Poll should retrieve the buffered event
201 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Close));
202
203 // After taking, it should be false again
204 assert!(!terminal.has_input());
205 }
206
207 #[test]
208 fn test_terminal_drain_events_into() {
209 use alloc::vec;
210
211 let backend = Headless::new(10, 10);
212 let mut terminal = Terminal::new(backend);
213
214 terminal.backend_mut().push_event(Event::Close);
215 terminal.backend_mut().push_event(Event::Resize(80, 25));
216
217 let mut buf = vec![Event::Close]; // pre-existing contents must be cleared
218 terminal.drain_events_into(&mut buf);
219 assert_eq!(buf, [Event::Close, Event::Resize(80, 25)]);
220
221 // The borrow ends at the call, so the terminal is immediately usable again.
222 assert_eq!(terminal.area(), Rect::new(0, 0, 80, 25));
223
224 // Draining again with nothing pending clears the buffer.
225 terminal.drain_events_into(&mut buf);
226 assert!(buf.is_empty());
227 }
228
229 #[test]
230 fn test_terminal_drain_events_into_applies_a_backend_resize() {
231 use alloc::vec::Vec;
232
233 let backend = Headless::new(10, 10);
234 let mut terminal = Terminal::new(backend);
235
236 terminal.backend_mut().push_event(Event::Close);
237 terminal.backend_mut().push_event(Event::Resize(4, 2));
238
239 let mut buf = Vec::new();
240 terminal.drain_events_into(&mut buf);
241 assert_eq!(buf, [Event::Close, Event::Resize(4, 2)]);
242 // The resize was applied on the way in, same as `poll`/`drain_events`, not just handed
243 // back as an inert event for the caller to notice and apply itself.
244 assert_eq!(terminal.size(), Size::new(4, 2));
245 }
246
247 #[test]
248 fn test_terminal_drain_events_into_does_not_reapply_a_requeued_resize() {
249 use alloc::vec::Vec;
250
251 let backend = ResizeCounting::new(10, 10);
252 let mut terminal = Terminal::new(backend);
253
254 terminal.backend_mut().inner.push_event(Event::Resize(4, 2));
255
256 let mut buf = Vec::new();
257 terminal.drain_events_into(&mut buf);
258 assert_eq!(buf, [Event::Resize(4, 2)]);
259 assert_eq!(terminal.backend().resize_calls, 1);
260
261 // A wrapper that drained the event and handed it back gets it again, but the resize is
262 // not applied a second time for the one logical event. See `poll_backend`.
263 terminal.requeue_events(buf.iter().cloned());
264 terminal.drain_events_into(&mut buf);
265 assert_eq!(buf, [Event::Resize(4, 2)]);
266 assert_eq!(terminal.backend().resize_calls, 1);
267 }
268
269 #[test]
270 fn test_terminal_drain_events_is_one_shot_and_fused() {
271 let backend = Headless::new(10, 10);
272 let mut terminal = Terminal::new(backend);
273
274 terminal.backend_mut().push_event(Event::Close);
275 terminal.backend_mut().push_event(Event::Close);
276
277 let mut drained = terminal.drain_events();
278 assert_eq!(drained.next(), Some(Event::Close));
279 assert_eq!(drained.next(), Some(Event::Close));
280 // Fused: repeated calls past exhaustion keep returning `None` rather than panicking.
281 assert_eq!(drained.next(), None);
282 assert_eq!(drained.next(), None);
283 drop(drained);
284
285 // One-shot: a second, independent call after the first already drained the queue sees
286 // only what was pushed since, not a second copy of anything already handed out.
287 terminal.backend_mut().push_event(Event::Close);
288 let second: Vec<_> = terminal.drain_events().collect();
289 assert_eq!(second, [Event::Close]);
290 }
291
292 #[test]
293 fn test_terminal_requeue_events_replays_before_the_backend() {
294 let backend = Headless::new(10, 10);
295 let mut terminal = Terminal::new(backend);
296
297 // Push directly to the backend first, so a requeued event has to come out ahead of it.
298 terminal.backend_mut().push_event(Event::Close);
299 terminal.requeue_events([Event::Resize(80, 25), Event::Resize(1, 1)]);
300
301 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(80, 25)));
302 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(1, 1)));
303 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Close));
304 assert_eq!(terminal.poll(Duration::ZERO), None);
305 }
306
307 #[test]
308 fn test_terminal_wait_for_input_buffers_the_event_instead_of_consuming_it() {
309 let backend = Headless::new(10, 10);
310 let mut terminal = Terminal::new(backend);
311
312 // Nothing queued: returns false rather than blocking (`Headless` ignores the timeout).
313 assert!(!terminal.wait_for_input(Duration::from_millis(1)));
314
315 terminal.backend_mut().push_event(Event::Close);
316 assert!(terminal.wait_for_input(Duration::MAX));
317 // Repeated calls stay true: the event was buffered, not handed out and lost.
318 assert!(terminal.wait_for_input(Duration::MAX));
319 assert!(terminal.has_input());
320
321 // A caller reading through the normal input API (as an app's own `update` would) still
322 // observes the exact event `wait_for_input` woke up for.
323 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Close));
324 assert!(!terminal.has_input());
325 }
326
327 #[test]
328 fn test_terminal_wait_for_input_applies_pending_resize() {
329 let backend = Headless::new(10, 10);
330 let mut terminal = Terminal::new(backend);
331
332 terminal.backend_mut().push_event(Event::Resize(4, 2));
333 assert!(terminal.wait_for_input(Duration::MAX));
334 // The grid resizes immediately, same as `poll`, even though the event is still buffered
335 // rather than consumed.
336 assert_eq!(terminal.size(), Size::new(4, 2));
337 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(4, 2)));
338 }
339
340 /// Wraps [`Headless`] and counts [`resize`](Output::resize) calls, so a test can prove
341 /// `Terminal` applies a backend resize at most once per logical `Event::Resize`, even when
342 /// the event is buffered by `wait_for_input` and then consumed by `poll` (retroglyph#959).
343 struct ResizeCounting {
344 inner: Headless,
345 resize_calls: usize,
346 }
347
348 impl ResizeCounting {
349 fn new(width: u16, height: u16) -> Self {
350 Self {
351 inner: Headless::new(width, height),
352 resize_calls: 0,
353 }
354 }
355 }
356
357 impl Output for ResizeCounting {
358 type Error = core::convert::Infallible;
359
360 fn draw_layers<'a, I>(&mut self, content: I) -> Result<(), Self::Error>
361 where
362 I: Iterator<Item = DrawCell<'a>>,
363 {
364 self.inner.draw_layers(content)
365 }
366
367 fn resize(&mut self, size: Size) {
368 self.resize_calls += 1;
369 self.inner.resize(size);
370 }
371
372 fn flush(&mut self) -> Result<(), Self::Error> {
373 self.inner.flush()
374 }
375
376 fn size(&self) -> Size {
377 self.inner.size()
378 }
379
380 fn clear(&mut self) -> Result<(), Self::Error> {
381 self.inner.clear()
382 }
383 }
384
385 impl Input for ResizeCounting {
386 fn poll_event(&mut self, timeout: Duration) -> Option<Event> {
387 self.inner.poll_event(timeout)
388 }
389
390 fn push_event(&mut self, event: Event) {
391 self.inner.push_event(event);
392 }
393 }
394
395 impl Cursor for ResizeCounting {}
396
397 #[test]
398 fn test_terminal_poll_does_not_reapply_resize_buffered_by_wait_for_input() {
399 let backend = ResizeCounting::new(10, 10);
400 let mut terminal = Terminal::new(backend);
401
402 terminal.backend_mut().push_event(Event::Resize(8, 2));
403 assert!(terminal.wait_for_input(Duration::ZERO));
404 assert_eq!(terminal.backend().resize_calls, 1);
405
406 // The event was only buffered, not consumed, by `wait_for_input`; `poll` must return the
407 // same event without resizing again.
408 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(8, 2)));
409 assert_eq!(terminal.backend().resize_calls, 1);
410 assert_eq!(terminal.size(), Size::new(8, 2));
411 assert_eq!(terminal.backend().size(), Size::new(8, 2));
412
413 // Exercise the rest of `ResizeCounting`'s `Output` passthrough too, so the mock's own
414 // plumbing (not just `resize_calls`) is covered rather than asserted by inspection.
415 terminal.present().unwrap();
416 terminal.backend_mut().clear().unwrap();
417 }
418
419 #[test]
420 fn test_terminal_poll_does_not_reapply_resize_already_matching_current_size() {
421 // Regression test for retroglyph#1291: a driver (`retroglyph-window`'s winit driver) can
422 // now call `Terminal::resize` itself before the matching `Event::Resize` is ever polled,
423 // purely so `Terminal::size` is never stale from the app's point of view. `poll_backend`
424 // must not pay for a second resize of a size that was already applied when the app later
425 // polls that same event.
426 let backend = ResizeCounting::new(10, 10);
427 let mut terminal = Terminal::new(backend);
428
429 // Simulate the driver: resize the terminal directly, then push the event a real driver
430 // would still deliver for an app that wants to react to it.
431 terminal.resize(8, 2);
432 assert_eq!(terminal.backend().resize_calls, 1);
433 terminal.backend_mut().push_event(Event::Resize(8, 2));
434
435 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(8, 2)));
436 assert_eq!(
437 terminal.backend().resize_calls,
438 1,
439 "poll must not resize again for an event whose size already matches"
440 );
441 assert_eq!(terminal.size(), Size::new(8, 2));
442 }
443
444 #[test]
445 fn test_terminal_poll_does_not_reapply_resize_from_requeue_events() {
446 let backend = ResizeCounting::new(10, 10);
447 let mut terminal = Terminal::new(backend);
448
449 terminal.backend_mut().inner.push_event(Event::Resize(9, 3));
450 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(9, 3)));
451 assert_eq!(terminal.backend().resize_calls, 1);
452
453 // A wrapper (e.g. `PerfOverlayApp`) hands the event straight back via `requeue_events`;
454 // the next `poll` must not resize a second time for it.
455 terminal.requeue_events([Event::Resize(9, 3)]);
456 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(9, 3)));
457 assert_eq!(terminal.backend().resize_calls, 1);
458 }
459
460 #[test]
461 fn test_terminal_retain_layer_survives_wait_for_input_then_poll_of_a_resize() {
462 let backend = Headless::new(10, 10);
463 let mut terminal = Terminal::new(backend);
464
465 terminal.backend_mut().push_event(Event::Resize(8, 2));
466 assert!(terminal.wait_for_input(Duration::ZERO));
467
468 // A caller that decides retention for this frame in between `wait_for_input` waking it
469 // up and the matching `poll` that actually reads the event (a plausible ordering: e.g.
470 // deciding retention from state gathered before reading input) must not have that
471 // decision silently undone by `poll` re-applying the already-handled resize.
472 terminal.retain_layer(0u8);
473 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(8, 2)));
474 assert_eq!(terminal.pending_layer_ops, [LayerOp::Retain]);
475 }
476
477 #[test]
478 fn test_terminal_requeue_events_interleaves_with_wait_for_input_lookahead() {
479 let backend = Headless::new(10, 10);
480 let mut terminal = Terminal::new(backend);
481
482 // `wait_for_input` buffers one event ahead of anything requeued afterward.
483 terminal.backend_mut().push_event(Event::Close);
484 assert!(terminal.wait_for_input(Duration::MAX));
485
486 // Requeueing now must not jump the queue ahead of the event `wait_for_input` already
487 // buffered: both go through the same internal queue, in call order.
488 terminal.requeue_events([Event::Resize(4, 2)]);
489
490 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Close));
491 assert_eq!(terminal.poll(Duration::ZERO), Some(Event::Resize(4, 2)));
492 assert_eq!(terminal.poll(Duration::ZERO), None);
493 }
494}