matchmaker/
event.rs

1use crate::Result;
2use crate::action::{Action, Actions, Count};
3use crate::binds::{BindMap};
4use crate::message::{Event, RenderCommand};
5use anyhow::bail;
6use crokey::{Combiner, KeyCombination, KeyCombinationFormat, key};
7use crossterm::event::{Event as CrosstermEvent, EventStream, KeyModifiers};
8use futures::stream::StreamExt;
9use log::{debug, error, info, warn};
10use ratatui::layout::Rect;
11use tokio::sync::mpsc;
12use tokio::time::{self};
13
14#[derive(Debug)]
15pub struct EventLoop {
16    txs: Vec<mpsc::UnboundedSender<RenderCommand>>,
17    tick_interval: time::Duration,
18
19    binds: BindMap,
20    combiner: Combiner,
21    fmt: KeyCombinationFormat,
22
23    paused: bool,
24    event_stream: Option<EventStream>,
25    controller_rx: mpsc::UnboundedReceiver<Event>,
26    controller_tx: mpsc::UnboundedSender<Event>,
27}
28
29impl Default for EventLoop {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl EventLoop {
36    pub fn new() -> Self {
37        let combiner = Combiner::default();
38        let fmt = KeyCombinationFormat::default();
39        let event_stream = Some(EventStream::new());
40        let (controller_tx, controller_rx) = tokio::sync::mpsc::unbounded_channel();
41
42
43        Self {
44            txs: vec![],
45            tick_interval: time::Duration::from_secs(1),
46
47            binds: BindMap::new(),
48            combiner,
49            fmt,
50            event_stream,
51            controller_rx,
52            controller_tx,
53
54            paused: false
55        }
56    }
57
58
59    pub fn add_tx(&mut self, handler: mpsc::UnboundedSender<RenderCommand>) -> &mut Self {
60        self.txs.push(handler);
61        self
62    }
63
64    pub fn clear_txs(&mut self) {
65        self.txs.clear();
66    }
67
68    pub fn set_tick_rate(&mut self, tick_rate: u8) -> &mut Self {
69        self.tick_interval = time::Duration::from_secs_f64(1.0 / tick_rate as f64);
70        self
71    }
72
73    pub fn get_controller(&self) -> mpsc::UnboundedSender<Event> {
74        self.controller_tx.clone()
75    }
76
77    fn handle_event(&mut self, e: Event) -> bool {
78        debug!("Received: {e}");
79
80        match e {
81            Event::Pause => {
82                self.paused = true;
83                self.send(RenderCommand::Ack);
84                self.event_stream = None;
85            }
86            Event::Refresh => {
87                self.send(RenderCommand::Refresh);
88            },
89            _ => {}
90        }
91        if let Some(actions) = self.binds.get(&e.into()) {
92            self.send_actions(actions);
93        }
94
95        self.paused
96    }
97
98    pub fn binds(&mut self, binds: BindMap) -> &mut Self {
99        self.binds = binds;
100        self
101    }
102
103    // todo: should its return type carry info
104    pub async fn run(&mut self) -> Result<()> {
105        let mut interval = time::interval(self.tick_interval);
106
107        loop {
108            while self.paused {
109                if let Some(event) = self.controller_rx.recv().await {
110                    if matches!(event, Event::Resume) {
111                        self.paused = false;
112                        self.send(RenderCommand::Ack);
113                        self.event_stream = Some(EventStream::new());
114                        continue;
115                    }
116                } else {
117                    error!("Event controller closed while paused.");
118                    break;
119                }
120            }
121
122            // prioritize pause. Any event that is still processing or already in render queue
123            while let Ok(event) = self.controller_rx.try_recv() {
124                if self.handle_event(event) {
125                }
126            };
127
128            self.txs.retain(|tx| !tx.is_closed());
129            if self.txs.is_empty() {
130                break;
131            }
132
133            let event = if let Some(stream) = &mut self.event_stream {
134                stream.next()
135            } else {
136                bail!("No event stream (this should be unreachable)");
137            };
138
139            tokio::select! {
140                _ = interval.tick() => {
141                    self.send(RenderCommand::Tick)
142                }
143
144                // In case ctrl-c manifests as a signal instead of a key
145                _ = tokio::signal::ctrl_c() => {
146                    if let Some(actions) = self.binds.get(&key!(ctrl-c).into()) {
147                        self.send_actions(actions);
148                    } else {
149                        self.send(RenderCommand::quit());
150                        info!("Received ctrl-c");
151                        break;
152                    }
153                }
154
155                Some(event) = self.controller_rx.recv() => {
156                    self.handle_event(event);
157                    // todo: note that our dynamic event handlers don't detect events originating outside of render currently, maybe we could reseed through render somehow
158                }
159
160                // Input ready
161                maybe_event = event => {
162                    match maybe_event {
163                        Some(Ok(event)) => {
164                            info!("Event {event:?}");
165                            match event {
166                                CrosstermEvent::Key(k) => {
167                                    info!("{k:?}");
168                                    if let Some(key) = self.combiner.transform(k) {
169                                        let key = KeyCombination::normalized(key);
170                                        if let Some(actions) = self.binds.get(&key.into()) {
171                                            self.send_actions(actions);
172                                        } else if let Some(c) = key_code_as_letter(key) {
173                                            self.send(RenderCommand::Input(c));
174                                        } else {
175                                            // a basic set of keys to prevent confusion
176                                            match key {
177                                                key!(ctrl-c) | key!(esc) => {
178                                                    info!("quitting");
179                                                    self.send(RenderCommand::quit());
180                                                    break;
181                                                }
182                                                key!(up) => self.send_action(Action::Up(Count(1))),
183                                                key!(down) => self.send_action(Action::Down(Count(1))),
184                                                key!(enter) => self.send_action(Action::Accept),
185                                                key!(right) => self.send_action(Action::ForwardChar),
186                                                key!(left) => self.send_action(Action::BackwardChar),
187                                                key!(ctrl-right) => self.send_action(Action::ForwardWord),
188                                                key!(ctrl-left) => self.send_action(Action::BackwardWord),
189                                                key!(backspace) => self.send_action(Action::DeleteChar),
190                                                key!(ctrl-h) => self.send_action(Action::DeleteWord),
191                                                key!(ctrl-u) => self.send_action(Action::Cancel),
192                                                key!(alt-h) => self.send_action(Action::Help("".to_string())),
193                                                _ => {}
194                                            }
195                                        }
196                                        info!("You typed {}", self.print_key(key));
197                                    }
198                                }
199                                CrosstermEvent::Mouse(mouse) => {
200                                    if let Some(actions) = self.binds.get(&mouse.into()) {
201                                        self.send_actions(actions);
202                                    }
203                                }
204                                CrosstermEvent::Resize(width, height) => {
205                                    self.send(RenderCommand::Resize(Rect::new(0, 0, width, height)));
206                                }
207                                // CrosstermEvent::FocusLost => {
208                                // }
209                                // CrosstermEvent::FocusGained => {
210                                // }
211                                // CrosstermEvent::Paste(_) => {}
212                                _ => {},
213                            }
214                        }
215                        Some(Err(e)) => warn!("Failed to read crossterm event: {e}"),
216                        None => {
217                            warn!("Reader closed");
218                            break
219                        }
220                    }
221                }
222            }
223        }
224        Ok(())
225    }
226
227    fn send(&self, action: RenderCommand) {
228        for tx in &self.txs {
229            tx.send(action.clone())
230            .unwrap_or_else(|_| debug!("Failed to send {action}"));
231        }
232    }
233
234    fn send_actions(&self, actions: &Actions) {
235        for action in actions.0.iter() {
236            self.send(action.into());
237        }
238    }
239
240    fn print_key(&self, key_combination: KeyCombination) -> String {
241        self.fmt.to_string(key_combination)
242    }
243
244    fn send_action(&self, action: Action) {
245        self.send(RenderCommand::Action(action));
246    }
247}
248
249fn key_code_as_letter(key: KeyCombination) -> Option<char> {
250    match key {
251        KeyCombination {
252            codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
253            modifiers: KeyModifiers::NONE,
254        } => Some(l),
255        KeyCombination {
256            codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
257            modifiers: KeyModifiers::SHIFT,
258        } => Some(l.to_ascii_uppercase()),
259        _ => None,
260    }
261}