envelope_cli/tui/
event.rs

1//! Event handling for the TUI
2//!
3//! This module handles terminal events (key presses, mouse events, resize)
4//! using crossterm's event system.
5
6use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent};
7use std::sync::mpsc;
8use std::thread;
9use std::time::{Duration, Instant};
10
11/// Terminal events
12#[derive(Debug, Clone)]
13pub enum Event {
14    /// Key press event
15    Key(KeyEvent),
16    /// Mouse event
17    Mouse(MouseEvent),
18    /// Terminal resize
19    Resize(u16, u16),
20    /// Tick event for periodic updates
21    Tick,
22}
23
24/// Event handler for terminal events
25pub struct EventHandler {
26    /// Event sender
27    #[allow(dead_code)]
28    sender: mpsc::Sender<Event>,
29    /// Event receiver
30    receiver: mpsc::Receiver<Event>,
31    /// Event thread handle
32    #[allow(dead_code)]
33    handler: thread::JoinHandle<()>,
34}
35
36impl EventHandler {
37    /// Create a new event handler with the specified tick rate
38    pub fn new(tick_rate: Duration) -> Self {
39        let (sender, receiver) = mpsc::channel();
40        let handler = {
41            let sender = sender.clone();
42            thread::spawn(move || {
43                let mut last_tick = Instant::now();
44                loop {
45                    // Calculate timeout for next tick
46                    let timeout = tick_rate
47                        .checked_sub(last_tick.elapsed())
48                        .unwrap_or(Duration::ZERO);
49
50                    // Poll for events
51                    if event::poll(timeout).expect("Failed to poll events") {
52                        match event::read().expect("Failed to read event") {
53                            CrosstermEvent::Key(key) if key.kind == KeyEventKind::Press => {
54                                if sender.send(Event::Key(key)).is_err() {
55                                    return;
56                                }
57                            }
58                            CrosstermEvent::Mouse(mouse) => {
59                                if sender.send(Event::Mouse(mouse)).is_err() {
60                                    return;
61                                }
62                            }
63                            CrosstermEvent::Resize(width, height) => {
64                                if sender.send(Event::Resize(width, height)).is_err() {
65                                    return;
66                                }
67                            }
68                            _ => {}
69                        }
70                    }
71
72                    // Send tick event if needed
73                    if last_tick.elapsed() >= tick_rate {
74                        if sender.send(Event::Tick).is_err() {
75                            return;
76                        }
77                        last_tick = Instant::now();
78                    }
79                }
80            })
81        };
82
83        Self {
84            sender,
85            receiver,
86            handler,
87        }
88    }
89
90    /// Get the next event (blocking)
91    pub fn next(&self) -> Result<Event, mpsc::RecvError> {
92        self.receiver.recv()
93    }
94
95    /// Try to get the next event (non-blocking)
96    #[allow(dead_code)]
97    pub fn try_next(&self) -> Result<Event, mpsc::TryRecvError> {
98        self.receiver.try_recv()
99    }
100}
101
102impl Default for EventHandler {
103    fn default() -> Self {
104        Self::new(Duration::from_millis(250))
105    }
106}