• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jzombie / term-wm / 20788957288

07 Jan 2026 04:43PM UTC coverage: 35.923% (+2.3%) from 33.582%
20788957288

push

github

web-flow
General UI improvements (#3)

* Rename to `DefaultDecorator`

* Add theme.rs

* Rename to term_color.rs

* Use static display order

* Add pill-like labels

* Migrate more state to state.rs

* Add `HeaderAction` struct

* Use same window title in title bar and window list

* Simplify window creation

* Prototype centralized output driver

* Prototype crash reporting

* Fix issue where dual image example would crash when moving windows

* Remove inner decorative frame in dual image example

* Preliminary support for window titles

* Extract window manager

* Fix issue where debug window would not auto-snap when other window snaps

* Fix issue where vertical resizing of tiles would become unresponsive

* Simplify window focusing

* Window header double-click toggles maximize/restore state

* Simplify io drivers

496 of 2597 new or added lines in 28 files covered. (19.1%)

19 existing lines in 8 files now uncovered.

2515 of 7001 relevant lines covered (35.92%)

2.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.86
/src/event_loop.rs
1
use std::io;
2
use std::time::Duration;
3

4
use crossterm::event::Event;
5

6
use crate::drivers::InputDriver;
7

8
pub enum ControlFlow {
9
    Continue,
10
    Quit,
11
}
12

13
/// A centralized event loop that drives the main UI thread.
14
///
15
/// This struct implements the "Message Pump" or "Game Loop" pattern. It is responsible for:
16
/// 1. Owning the main execution thread.
17
/// 2. Polling the input driver for user events (keyboard, mouse, resize).
18
/// 3. Dispatching those events to a provided handler closure.
19
///
20
/// Note: This loop controls the synchronous UI flow. Background tasks (like reading
21
/// from subprocess PTYs) run in separate threads with their own loops to avoid
22
/// blocking the UI, but they feed data into the state that this loop renders.
23
pub struct EventLoop<D> {
24
    driver: D,
25
    poll_interval: Duration,
26
}
27

28
impl<D: InputDriver> EventLoop<D> {
29
    pub fn new(driver: D, poll_interval: Duration) -> Self {
2✔
30
        Self {
2✔
31
            driver,
2✔
32
            poll_interval,
2✔
33
        }
2✔
34
    }
2✔
35

36
    pub fn poll(&mut self) -> io::Result<Option<Event>> {
2✔
37
        if self.driver.poll(self.poll_interval)? {
2✔
38
            Ok(Some(self.driver.read()?))
1✔
39
        } else {
40
            Ok(None)
1✔
41
        }
42
    }
2✔
43

44
    pub fn driver(&mut self) -> &mut D {
×
45
        &mut self.driver
×
46
    }
×
47

48
    /// Runs the application loop, taking control of the current thread.
49
    ///
50
    /// This method establishes the "One Loop to Rule Them All" architecture:
51
    /// 1. **Polling**: It is the only place in the app that calls `driver.poll()` or `driver.read()`.
52
    /// 2. **Dispatching**: When an event arrives, it is passed to the `handler` closure.
53
    /// 3. **Routing**: The handler is responsible for routing the event to the appropriate
54
    ///    window or component (e.g., via `WindowManager`).
55
    ///
56
    /// The `handler` is called with:
57
    /// - `Some(event)` when an input event occurs.
58
    /// - `None` when the poll interval elapses without an event (useful for drawing/animations).
59
    pub fn run<F>(&mut self, mut handler: F) -> io::Result<()>
1✔
60
    where
1✔
61
        F: FnMut(&mut D, Option<Event>) -> io::Result<ControlFlow>,
1✔
62
    {
63
        loop {
64
            if let ControlFlow::Quit = handler(&mut self.driver, None)? {
1✔
65
                break;
1✔
66
            }
×
67

68
            if self.driver.poll(self.poll_interval)? {
×
69
                // Drain the event queue to prevent input lag during high-frequency event bursts
70
                // (e.g. mouse drags, scrolling). If we only processed one event per poll,
71
                // the rendering loop would fall behind the input stream.
72
                loop {
73
                    let event = self.driver.read()?;
×
74
                    if let ControlFlow::Quit = handler(&mut self.driver, Some(event))? {
×
75
                        return Ok(());
×
76
                    }
×
77
                    if !self.driver.poll(Duration::from_millis(0))? {
×
78
                        break;
×
79
                    }
×
80
                }
81
            }
×
82
        }
83
        Ok(())
1✔
84
    }
1✔
85
}
86

87
#[cfg(test)]
88
mod tests {
89
    use super::*;
90
    use crate::drivers::InputDriver;
91
    use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
92
    use std::time::Duration;
93

94
    struct DummyDriver {
95
        polls: usize,
96
    }
97

98
    impl DummyDriver {
99
        fn new() -> Self {
2✔
100
            Self { polls: 0 }
2✔
101
        }
2✔
102
    }
103

104
    impl InputDriver for DummyDriver {
105
        fn poll(&mut self, _timeout: Duration) -> std::io::Result<bool> {
2✔
106
            self.polls += 1;
2✔
107
            // return true only once
108
            Ok(self.polls == 1)
2✔
109
        }
2✔
110

111
        fn read(&mut self) -> std::io::Result<Event> {
1✔
112
            Ok(Event::Key(KeyEvent::new(
1✔
113
                KeyCode::Char('x'),
1✔
114
                KeyModifiers::NONE,
1✔
115
            )))
1✔
116
        }
1✔
117

NEW
118
        fn next_key(&mut self) -> std::io::Result<KeyEvent> {
×
NEW
119
            Ok(KeyEvent::new(KeyCode::Char('x'), KeyModifiers::NONE))
×
NEW
120
        }
×
121

NEW
122
        fn next_mouse(&mut self) -> std::io::Result<crossterm::event::MouseEvent> {
×
NEW
123
            Err(io::Error::other("not implemented"))
×
NEW
124
        }
×
125
    }
126

127
    #[test]
128
    fn poll_returns_event_when_available() {
1✔
129
        let d = DummyDriver::new();
1✔
130
        let mut ev = EventLoop::new(d, Duration::from_millis(0));
1✔
131
        // first poll should cause read to be called
132
        let res = ev.poll().unwrap();
1✔
133
        assert!(res.is_some());
1✔
134
        // second poll should return None
135
        let res2 = ev.poll().unwrap();
1✔
136
        assert!(res2.is_none());
1✔
137
    }
1✔
138

139
    #[test]
140
    fn run_calls_handler_and_respects_quit() {
1✔
141
        let d = DummyDriver::new();
1✔
142
        let mut ev = EventLoop::new(d, Duration::from_millis(0));
1✔
143
        let mut count = 0;
1✔
144
        let handler =
1✔
145
            |_driver: &mut DummyDriver, _evt: Option<Event>| -> std::io::Result<ControlFlow> {
1✔
146
                count += 1;
1✔
147
                Ok(ControlFlow::Quit)
1✔
148
            };
1✔
149
        ev.run(handler).unwrap();
1✔
150
        assert!(count >= 1);
1✔
151
    }
1✔
152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc