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;