Skip to main content

dot/tui/
event.rs

1use crossterm::event::{Event as CEvent, EventStream, KeyEventKind, MouseEventKind};
2use futures::StreamExt;
3use tokio::sync::mpsc;
4
5use crate::agent::AgentEvent;
6
7pub enum AppEvent {
8    Key(crossterm::event::KeyEvent),
9    Mouse(crossterm::event::MouseEvent),
10    Paste(String),
11    Tick,
12    Agent(AgentEvent),
13    Resize(u16, u16),
14}
15
16impl Default for EventHandler {
17    fn default() -> Self {
18        Self::new()
19    }
20}
21
22pub struct EventHandler {
23    rx: mpsc::UnboundedReceiver<AppEvent>,
24    tx: mpsc::UnboundedSender<AppEvent>,
25    _task: tokio::task::JoinHandle<()>,
26}
27
28impl EventHandler {
29    pub fn new() -> Self {
30        let (tx, rx) = mpsc::unbounded_channel();
31        let event_tx = tx.clone();
32
33        let task = tokio::spawn(async move {
34            let mut reader = EventStream::new();
35            let mut tick = tokio::time::interval(std::time::Duration::from_millis(16));
36
37            loop {
38                tokio::select! {
39                    maybe_event = reader.next() => {
40                        match maybe_event {
41                            Some(Ok(CEvent::Key(key))) => {
42                                if key.kind == KeyEventKind::Press
43                                    && event_tx.send(AppEvent::Key(key)).is_err()
44                                {
45                                    return;
46                                }
47                            }
48                            Some(Ok(CEvent::Mouse(mouse))) => {
49                                let forward = matches!(
50                                    mouse.kind,
51                                    MouseEventKind::Down(_)
52                                        | MouseEventKind::Up(_)
53                                        | MouseEventKind::Drag(_)
54                                        | MouseEventKind::Moved
55                                        | MouseEventKind::ScrollUp
56                                        | MouseEventKind::ScrollDown
57                                );
58                                if forward
59                                    && event_tx.send(AppEvent::Mouse(mouse)).is_err()
60                                {
61                                    return;
62                                }
63                            }
64                            Some(Ok(CEvent::Paste(text))) => {
65                                if event_tx.send(AppEvent::Paste(text)).is_err() {
66                                    return;
67                                }
68                            }
69                            Some(Ok(CEvent::Resize(w, h))) => {
70                                if event_tx.send(AppEvent::Resize(w, h)).is_err() {
71                                    return;
72                                }
73                            }
74                            Some(Ok(_)) => {}
75                            Some(Err(_)) => return,
76                            None => return,
77                        }
78                    }
79                    _ = tick.tick() => {
80                        if event_tx.send(AppEvent::Tick).is_err() {
81                            return;
82                        }
83                    }
84                }
85            }
86        });
87
88        Self {
89            rx,
90            tx,
91            _task: task,
92        }
93    }
94
95    pub async fn next(&mut self) -> Option<AppEvent> {
96        self.rx.recv().await
97    }
98
99    pub fn tx(&self) -> mpsc::UnboundedSender<AppEvent> {
100        self.tx.clone()
101    }
102}