Skip to main content

cai_tui/
event.rs

1//! Event handling for TUI
2//!
3//! Provides keyboard and event loop handling for the terminal UI.
4
5use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind};
6use std::time::Duration;
7use tokio::sync::mpsc;
8
9/// Terminal event
10#[derive(Debug, Clone, PartialEq)]
11pub enum Event {
12    /// Key press event
13    Key(KeyEvent),
14    /// Tick event (periodic)
15    Tick,
16}
17
18/// Event handler for terminal events
19pub struct EventHandler {
20    /// Event sender channel (kept to hold channel open)
21    _sender: mpsc::UnboundedSender<Event>,
22    /// Event receiver channel
23    receiver: mpsc::UnboundedReceiver<Event>,
24    /// Event handler task handle
25    _handle: tokio::task::JoinHandle<()>,
26}
27
28impl EventHandler {
29    /// Create a new event handler
30    ///
31    /// # Arguments
32    ///
33    /// * `tick_rate` - Milliseconds between tick events
34    pub fn new(tick_rate: u64) -> Self {
35        let (sender, receiver) = mpsc::unbounded_channel();
36        let sender_clone = sender.clone();
37        let _handle = tokio::spawn(async move {
38            let mut tick_interval = tokio::time::interval(Duration::from_millis(tick_rate));
39            loop {
40                // Wait for next tick or event
41                tokio::select! {
42                    _ = tick_interval.tick() => {
43                        if sender_clone.send(Event::Tick).is_err() {
44                            break;
45                        }
46                    }
47                }
48            }
49        });
50
51        Self {
52            _sender: sender,
53            receiver,
54            _handle,
55        }
56    }
57
58    /// Get the next event
59    ///
60    /// Returns events in priority order:
61    /// 1. Key events (highest priority)
62    /// 2. Tick events (lowest priority)
63    pub async fn next(&mut self) -> Event {
64        // Check for key events with timeout
65        let key_event = tokio::task::spawn_blocking(move || {
66            if event::poll(Duration::from_millis(10)).ok()? {
67                if let CrosstermEvent::Key(key) = event::read().ok()? {
68                    // Only handle key press events, ignore repeat/release
69                    if key.kind == KeyEventKind::Press {
70                        return Some(Some(Event::Key(key)));
71                    }
72                }
73            }
74            Some(None)
75        })
76        .await
77        .ok()
78        .flatten();
79
80        if let Some(Some(event)) = key_event {
81            return event;
82        }
83
84        // Wait for next tick
85        self.receiver.recv().await.unwrap_or(Event::Tick)
86    }
87}