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