rustact/events/
mod.rs

1use std::time::Duration;
2
3use crossterm::event::{
4    Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent,
5    MouseEventKind,
6};
7use tokio::sync::broadcast;
8use tracing::trace;
9
10#[derive(Clone, Debug)]
11pub enum FrameworkEvent {
12    Key(KeyEvent),
13    Mouse(MouseEvent),
14    Resize(u16, u16),
15    Tick,
16}
17
18#[derive(Clone)]
19pub struct EventBus {
20    tx: broadcast::Sender<FrameworkEvent>,
21}
22
23impl EventBus {
24    pub fn new(buffer: usize) -> Self {
25        let (tx, _) = broadcast::channel(buffer);
26        Self { tx }
27    }
28
29    pub fn publish(&self, event: FrameworkEvent) {
30        trace!(event = ?event, "publishing framework event");
31        let _ = self.tx.send(event);
32    }
33
34    pub fn subscribe(&self) -> broadcast::Receiver<FrameworkEvent> {
35        self.tx.subscribe()
36    }
37}
38
39pub fn map_terminal_event(event: CrosstermEvent) -> Option<FrameworkEvent> {
40    match event {
41        CrosstermEvent::Key(key) => Some(FrameworkEvent::Key(key)),
42        CrosstermEvent::Mouse(mouse) => Some(FrameworkEvent::Mouse(mouse)),
43        CrosstermEvent::Resize(cols, rows) => Some(FrameworkEvent::Resize(cols, rows)),
44        CrosstermEvent::FocusGained | CrosstermEvent::FocusLost | CrosstermEvent::Paste(_) => None,
45    }
46}
47
48pub fn is_ctrl_c(event: &FrameworkEvent) -> bool {
49    match event {
50        FrameworkEvent::Key(key) => match key.code {
51            KeyCode::Char('c') | KeyCode::Char('C') => {
52                key.modifiers.contains(KeyModifiers::CONTROL)
53            }
54            _ => false,
55        },
56        _ => false,
57    }
58}
59
60pub fn is_mouse_click(event: &FrameworkEvent, button: MouseButton) -> bool {
61    matches!(
62        event,
63        FrameworkEvent::Mouse(mouse)
64            if matches!(mouse.kind, MouseEventKind::Down(btn) if btn == button)
65    )
66}
67
68pub fn mouse_scroll_delta(event: &FrameworkEvent) -> i32 {
69    if let FrameworkEvent::Mouse(mouse) = event {
70        match mouse.kind {
71            MouseEventKind::ScrollUp => 1,
72            MouseEventKind::ScrollDown => -1,
73            _ => 0,
74        }
75    } else {
76        0
77    }
78}
79
80pub fn mouse_position(event: &FrameworkEvent) -> Option<(u16, u16)> {
81    if let FrameworkEvent::Mouse(mouse) = event {
82        Some((mouse.column, mouse.row))
83    } else {
84        None
85    }
86}
87
88pub const DEFAULT_TICK_RATE: Duration = Duration::from_millis(250);
89
90#[cfg(test)]
91mod tests;