Skip to main content

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}