Skip to main content

rush_sync_server/input/
mod.rs

1pub mod keyboard;
2pub mod state;
3
4use crossterm::event::{self as crossterm_event, Event as CrosstermEvent, KeyEvent, MouseEventKind};
5use std::sync::OnceLock;
6use tokio::sync::mpsc::{self, Sender};
7use tokio::time::{interval, Duration, Instant};
8
9#[derive(Debug)]
10pub enum AppEvent {
11    Input(KeyEvent),
12    MouseScrollUp,
13    MouseScrollDown,
14    Tick,
15    Resize(u16, u16),
16    /// Background progress message from async commands (start all, stop all, etc.)
17    Progress(String),
18}
19
20// ===== Global Progress Channel =====
21// Commands can send progress messages to the TUI without blocking.
22
23static PROGRESS_TX: OnceLock<mpsc::UnboundedSender<String>> = OnceLock::new();
24
25/// Initialize the global progress channel. Returns the receiver.
26/// Called once by ScreenManager at startup.
27pub fn init_progress_channel() -> mpsc::UnboundedReceiver<String> {
28    let (tx, rx) = mpsc::unbounded_channel();
29    let _ = PROGRESS_TX.set(tx);
30    rx
31}
32
33/// Send a progress message to the TUI. Safe to call from any thread/task.
34/// Returns false if the channel is not initialized or closed.
35pub fn send_progress(message: String) -> bool {
36    if let Some(tx) = PROGRESS_TX.get() {
37        tx.send(message).is_ok()
38    } else {
39        false
40    }
41}
42
43pub struct EventHandler {
44    rx: mpsc::Receiver<AppEvent>,
45    shutdown_tx: Vec<Sender<()>>,
46}
47
48impl EventHandler {
49    pub fn new(tick_rate: Duration) -> Self {
50        let (tx, rx) = mpsc::channel(100);
51        let mut shutdown_tx = Vec::new();
52
53        // Input event handler
54        let (input_shutdown_tx, input_shutdown_rx) = mpsc::channel(1);
55        shutdown_tx.push(input_shutdown_tx);
56        Self::spawn_input_handler(tx.clone(), input_shutdown_rx);
57
58        // Tick handler
59        let (tick_shutdown_tx, tick_shutdown_rx) = mpsc::channel(1);
60        shutdown_tx.push(tick_shutdown_tx);
61        Self::spawn_tick_handler(tx, tick_rate, tick_shutdown_rx);
62
63        EventHandler { rx, shutdown_tx }
64    }
65
66    fn spawn_input_handler(tx: mpsc::Sender<AppEvent>, mut shutdown_rx: mpsc::Receiver<()>) {
67        tokio::spawn(async move {
68            let (mut last_key_time, mut last_resize_time) = (Instant::now(), Instant::now());
69            let (key_interval, resize_interval) =
70                (Duration::from_millis(16), Duration::from_millis(50));
71
72            loop {
73                tokio::select! {
74                    _ = shutdown_rx.recv() => break,
75                    _ = async {
76                        if crossterm_event::poll(Duration::from_millis(99)).unwrap_or(false) {
77                            if let Ok(event) = crossterm_event::read() {
78                                let now = Instant::now();
79                                match event {
80                                    CrosstermEvent::Key(key) if now.duration_since(last_key_time) >= key_interval => {
81                                        let _ = tx.send(AppEvent::Input(key)).await;
82                                        last_key_time = now;
83                                    }
84                                    CrosstermEvent::Mouse(mouse) => {
85                                        match mouse.kind {
86                                            MouseEventKind::ScrollUp => {
87                                                let _ = tx.send(AppEvent::MouseScrollUp).await;
88                                            }
89                                            MouseEventKind::ScrollDown => {
90                                                let _ = tx.send(AppEvent::MouseScrollDown).await;
91                                            }
92                                            _ => {}
93                                        }
94                                    }
95                                    CrosstermEvent::Resize(w, h) if now.duration_since(last_resize_time) >= resize_interval => {
96                                        let _ = tx.send(AppEvent::Resize(w, h)).await;
97                                        last_resize_time = now;
98                                    }
99                                    _ => {}
100                                }
101                            }
102                        }
103                    } => {}
104                }
105            }
106        });
107    }
108
109    fn spawn_tick_handler(
110        tx: mpsc::Sender<AppEvent>,
111        tick_rate: Duration,
112        mut shutdown_rx: mpsc::Receiver<()>,
113    ) {
114        tokio::spawn(async move {
115            let mut interval = interval(tick_rate);
116            loop {
117                tokio::select! {
118                    _ = shutdown_rx.recv() => break,
119                    _ = interval.tick() => { let _ = tx.send(AppEvent::Tick).await; }
120                }
121            }
122        });
123    }
124
125    pub async fn next(&mut self) -> Option<AppEvent> {
126        self.rx.recv().await
127    }
128
129    pub async fn shutdown(&mut self) {
130        for tx in &self.shutdown_tx {
131            let _ = tx.send(()).await;
132        }
133    }
134}