matchmaker/
event.rs

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