Skip to main content

void_audit_tui/
event.rs

1//! Event handling for the audit TUI.
2//!
3//! Adapted from void-graph TUI, which was based on
4//! [Serie](https://github.com/lusingander/serie) by lusingander.
5
6use std::{
7    fmt::{self, Debug, Formatter},
8    sync::mpsc,
9    thread,
10};
11
12use ratatui::crossterm::event::KeyEvent;
13use serde::{
14    de::{self, Deserializer, Visitor},
15    Deserialize,
16};
17
18/// Low-level application events from terminal.
19pub enum AppEvent {
20    Key(KeyEvent),
21    Resize(usize, usize),
22    Quit,
23}
24
25/// Sender handle for events.
26#[derive(Clone)]
27pub struct Sender {
28    tx: mpsc::Sender<AppEvent>,
29}
30
31impl Sender {
32    pub fn send(&self, event: AppEvent) {
33        let _ = self.tx.send(event);
34    }
35}
36
37impl Debug for Sender {
38    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
39        write!(f, "Sender")
40    }
41}
42
43/// Receiver handle for events.
44pub struct Receiver {
45    rx: mpsc::Receiver<AppEvent>,
46}
47
48impl Receiver {
49    pub fn recv(&self) -> AppEvent {
50        self.rx.recv().unwrap_or(AppEvent::Quit)
51    }
52}
53
54/// Initialize event system with background event reader thread.
55pub fn init() -> (Sender, Receiver) {
56    let (tx, rx) = mpsc::channel();
57    let tx = Sender { tx };
58    let rx = Receiver { rx };
59
60    let event_tx = tx.clone();
61    thread::spawn(move || loop {
62        match ratatui::crossterm::event::read() {
63            Ok(e) => match e {
64                ratatui::crossterm::event::Event::Key(key) => {
65                    event_tx.send(AppEvent::Key(key));
66                }
67                ratatui::crossterm::event::Event::Resize(w, h) => {
68                    event_tx.send(AppEvent::Resize(w as usize, h as usize));
69                }
70                _ => {}
71            },
72            Err(_) => {
73                break;
74            }
75        }
76    });
77
78    (tx, rx)
79}
80
81/// High-level semantic user events.
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub enum UserEvent {
84    ForceQuit,
85    Quit,
86    HelpToggle,
87    Cancel,
88    Close,
89    NavigateUp,
90    NavigateDown,
91    GoToTop,
92    GoToBottom,
93    ScrollUp,
94    ScrollDown,
95    PageUp,
96    PageDown,
97    HalfPageUp,
98    HalfPageDown,
99    Confirm,
100}
101
102impl<'de> Deserialize<'de> for UserEvent {
103    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104    where
105        D: Deserializer<'de>,
106    {
107        struct UserEventVisitor;
108
109        impl<'de> Visitor<'de> for UserEventVisitor {
110            type Value = UserEvent;
111
112            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
113                formatter.write_str("a string representing a user event")
114            }
115
116            fn visit_str<E>(self, value: &str) -> Result<UserEvent, E>
117            where
118                E: de::Error,
119            {
120                match value {
121                    "force_quit" => Ok(UserEvent::ForceQuit),
122                    "quit" => Ok(UserEvent::Quit),
123                    "help_toggle" => Ok(UserEvent::HelpToggle),
124                    "cancel" => Ok(UserEvent::Cancel),
125                    "close" => Ok(UserEvent::Close),
126                    "navigate_up" => Ok(UserEvent::NavigateUp),
127                    "navigate_down" => Ok(UserEvent::NavigateDown),
128                    "go_to_top" => Ok(UserEvent::GoToTop),
129                    "go_to_bottom" => Ok(UserEvent::GoToBottom),
130                    "scroll_up" => Ok(UserEvent::ScrollUp),
131                    "scroll_down" => Ok(UserEvent::ScrollDown),
132                    "page_up" => Ok(UserEvent::PageUp),
133                    "page_down" => Ok(UserEvent::PageDown),
134                    "half_page_up" => Ok(UserEvent::HalfPageUp),
135                    "half_page_down" => Ok(UserEvent::HalfPageDown),
136                    "confirm" => Ok(UserEvent::Confirm),
137                    _ => {
138                        let msg = format!("Unknown user event: {}", value);
139                        Err(de::Error::custom(msg))
140                    }
141                }
142            }
143        }
144
145        deserializer.deserialize_str(UserEventVisitor)
146    }
147}