matchmaker/
event.rs

1use std::collections::HashMap;
2
3use crate::Result;
4use crate::action::{Actions};
5use crate::binds::{BindMap};
6use crate::message::{Event, RenderCommand};
7use anyhow::bail;
8use crokey::{Combiner, KeyCombination, KeyCombinationFormat, key};
9use crossterm::event::{Event as CrosstermEvent, EventStream};
10use futures::stream::StreamExt;
11use log::{debug, error, info, warn};
12use tokio::sync::mpsc;
13use tokio::time::{self};
14
15#[derive(Debug)]
16pub struct EventLoop {
17    txs: Vec<mpsc::UnboundedSender<RenderCommand>>,
18    tick_interval: time::Duration,
19    
20    binds: BindMap,
21    combiner: Combiner,
22    fmt: KeyCombinationFormat,
23    
24    paused: bool,
25    event_stream: Option<EventStream>,
26    controller_rx: mpsc::UnboundedReceiver<Event>,
27}
28
29impl EventLoop {
30    pub fn new(
31        handler_channels: impl IntoIterator<Item = mpsc::UnboundedSender<RenderCommand>>,
32        tick_rate: u16,
33    ) -> (Self, mpsc::UnboundedSender<Event>) {
34        let combiner = Combiner::default();
35        let fmt = KeyCombinationFormat::default();
36        let event_stream = Some(EventStream::new());
37        let (controller_tx, controller_rx) = tokio::sync::mpsc::unbounded_channel();
38        
39        let new = Self {
40            txs: handler_channels.into_iter().collect(),
41            tick_interval: time::Duration::from_secs_f64(1.0 / tick_rate as f64),
42            
43            binds: HashMap::new(),
44            combiner,
45            fmt,
46            event_stream,
47            controller_rx,
48            
49            paused: false
50        };
51        (new, controller_tx)
52    }
53    
54    fn handle_event(&mut self, e: Event) -> bool {
55        debug!("Received: {e}");
56
57        match e {
58            Event::Pause => {
59                self.paused = true;
60                self.send(RenderCommand::Ack);
61                self.event_stream = None;
62            }
63            Event::Refresh => {
64                self.send(RenderCommand::Refresh);
65            },
66            _ => {}
67        }
68        if let Some(actions) = self.binds.get(&e.into()) {
69            self.send_actions(actions);
70        }
71        
72        self.paused
73    }
74    
75    pub fn binds(&mut self, binds: BindMap) -> &mut Self {
76        self.binds = binds;
77        self
78    }
79    
80    // todo: should its return type carry info
81    pub async fn run(&mut self) -> Result<()> {
82        let mut interval = time::interval(self.tick_interval);
83        
84        loop {
85            while self.paused {
86                if let Some(picker_event) = self.controller_rx.recv().await {
87                    if matches!(picker_event, Event::Resume) {
88                        self.paused = false;
89                        self.send(RenderCommand::Ack);
90                        self.event_stream = Some(EventStream::new());
91                        continue;
92                    }
93                } else {
94                    error!("Event controller closed while paused.");
95                    break;
96                }
97            }
98            
99            // prioritize pause. Any event that is still processing or already in render queue 
100            while let Ok(picker_event) = self.controller_rx.try_recv() {
101                if self.handle_event(picker_event) {
102                }
103            };
104            
105            self.txs.retain(|tx| !tx.is_closed());
106            if self.txs.is_empty() {
107                break;
108            }
109            
110            let event = if let Some(stream) = &mut self.event_stream {
111                stream.next()
112            } else {
113                bail!("No event stream (this should be unreachable)");
114            };
115            
116            tokio::select! {
117                _ = interval.tick() => {
118                    self.send(RenderCommand::Tick)
119                }
120                
121                // In case ctrl-c manifests as a signal instead of a key
122                _ = tokio::signal::ctrl_c() => {
123                    if let Some(actions) = self.binds.get(&key!(ctrl-c).into()) {
124                        self.send_actions(actions);
125                    } else {
126                        self.send(RenderCommand::quit());
127                        info!("Received ctrl-c");
128                        break;
129                    }
130                }
131                
132                Some(picker_event) = self.controller_rx.recv() => {
133                    self.handle_event(picker_event);
134                    // todo: note that our dynamic event handlers don't detect events originating outside of render currently, maybe we could reseed through render somehow
135                }
136                
137                // Input ready
138                maybe_event = event => {
139                    match maybe_event {
140                        Some(Ok(event)) => {
141                            info!("Event {event:?}");
142                            match event {
143                                CrosstermEvent::Key(k) => {
144                                    info!("{k:?}");
145                                    if let Some(key) = self.combiner.transform(k) {
146                                        let key = KeyCombination::normalized(key);
147                                        if let Some(actions) = self.binds.get(&key.into()) {
148                                            self.send_actions(actions);
149                                        } else if let Some(c) = key.as_letter() {
150                                            self.send(RenderCommand::Input(c));
151                                        } else {
152                                            match key {
153                                                key!(ctrl-c) | key!(esc) => {
154                                                    info!("quitting");
155                                                    self.send(RenderCommand::quit());
156                                                    break;
157                                                }
158                                                _ => {}
159                                            }
160                                        }
161                                        info!("You typed {}", self.print_key(key));
162                                    }
163                                }
164                                CrosstermEvent::Mouse(mouse) => {
165                                    if let Some(actions) = self.binds.get(&mouse.into()) {
166                                        self.send_actions(actions);
167                                    }
168                                }
169                                #[allow(unused)]
170                                CrosstermEvent::Resize(width, height) => {
171                                    // self.send_actions(&[Action::Resize(ratatui::layout::Rect::new(0, 0, width, height))].into());
172                                }
173                                CrosstermEvent::FocusLost => {
174                                }
175                                CrosstermEvent::FocusGained => {
176                                }
177                                CrosstermEvent::Paste(_) => {}
178                            }
179                        }
180                        Some(Err(e)) => warn!("Failed to read crossterm event: {e}"),
181                        None => {
182                            warn!("Reader closed");
183                            break
184                        }
185                    }
186                }
187            }
188        }
189        Ok(())
190    }
191    
192    fn send(&self, action: RenderCommand) {
193        for tx in &self.txs {
194            tx.send(action.clone())
195            .unwrap_or_else(|_| debug!("Failed to send {action}"));
196        }
197    }
198    
199    fn send_actions(&self, actions: &Actions) {
200        for action in actions.0.iter() {
201            self.send(action.into());
202        }
203    }
204    
205    fn print_key(&self, key_combination: KeyCombination) -> String {
206        self.fmt.to_string(key_combination)
207    }
208}