1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
use anyhow::Result;
use crossterm::event::{self, Event, KeyEventKind};
use ratatui::prelude::*;
use std::time::Duration;
use crate::app::App;
use crate::input::handle_key_event;
use crate::mouse::handle_mouse_event;
use crate::ui;
/// The main event loop: multiplexes terminal events, serial RX, and ticks.
pub struct EventLoop {
tick_rate: Duration,
}
impl EventLoop {
pub fn new() -> Self {
Self {
tick_rate: Duration::from_millis(16), // ~60 FPS
}
}
/// Run the event loop until the app signals quit.
pub fn run<B: Backend>(
&mut self,
terminal: &mut Terminal<B>,
app: &mut App,
) -> Result<()>
where
<B as Backend>::Error: Send + Sync + 'static,
{
let mut needs_render = true;
loop {
// Drain serial events
let had_serial = app.poll_serial();
if had_serial {
needs_render = true;
}
// Drain ALL pending terminal events before rendering.
while event::poll(Duration::ZERO)? {
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => {
handle_key_event(app, key);
needs_render = true;
}
Event::Mouse(mouse) => {
handle_mouse_event(app, mouse);
needs_render = true;
}
Event::Resize(_, _) => {
needs_render = true;
}
_ => {}
}
}
// Check quit before render
if app.should_quit {
app.disconnect();
return Ok(());
}
// Only render when state has actually changed
if needs_render {
terminal.draw(|frame| ui::render(app, frame))?;
needs_render = false;
}
// Sleep until next event or tick
match event::poll(self.tick_rate)? {
true => {
// Event arrived — will be drained next iteration
match event::read()? {
Event::Key(key) if key.kind == KeyEventKind::Press => {
handle_key_event(app, key);
needs_render = true;
}
Event::Mouse(mouse) => {
handle_mouse_event(app, mouse);
needs_render = true;
}
Event::Resize(_, _) => {
needs_render = true;
}
_ => {}
}
}
false => {
// Tick expired — check serial and re-render if there's
// a status message timer or reconnect animation
if app.status_message.is_some() || app.is_reconnecting() {
needs_render = true;
}
}
}
}
}
}