1use crate::Result;
2use crate::action::{Action, ActionExt, 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 Self {
44 txs: vec![],
45 tick_interval: time::Duration::from_secs(1),
46
47 binds: BindMap::new(),
48 combiner,
49 fmt,
50 event_stream: None, controller_rx,
52 controller_tx,
53
54 mouse_events: false,
55 paused: false,
56 }
57 }
58
59 pub fn with_binds(binds: BindMap<A>) -> Self {
60 let mut ret = Self::new();
61 ret.binds = binds;
62 ret
63 }
64
65 pub fn with_tick_rate(mut self, tick_rate: u8) -> Self {
66 self.tick_interval = time::Duration::from_secs_f64(1.0 / tick_rate as f64);
67 self
68 }
69
70 pub fn add_tx(&mut self, handler: mpsc::UnboundedSender<RenderCommand<A>>) -> &mut Self {
71 self.txs.push(handler);
72 self
73 }
74
75 pub fn with_mouse_events(mut self) -> Self {
76 self.mouse_events = true;
77 self
78 }
79
80 pub fn clear_txs(&mut self) {
81 self.txs.clear();
82 }
83
84 pub fn get_controller(&self) -> mpsc::UnboundedSender<Event> {
85 self.controller_tx.clone()
86 }
87
88 fn handle_event(&mut self, e: Event) -> bool {
89 debug!("Received: {e}");
90
91 match e {
92 Event::Pause => {
93 self.paused = true;
94 self.send(RenderCommand::Ack);
95 self.event_stream = None;
96 }
97 Event::Refresh => {
98 self.send(RenderCommand::Refresh);
99 }
100 _ => {}
101 }
102 if let Some(actions) = self.binds.get(&e.into()) {
103 self.send_actions(actions);
104 }
105
106 self.paused
107 }
108
109 pub fn binds(&mut self, binds: BindMap<A>) -> &mut Self {
110 self.binds = binds;
111 self
112 }
113
114 pub async fn run(&mut self) -> Result<()> {
116 self.event_stream = Some(EventStream::new());
117 let mut interval = time::interval(self.tick_interval);
118
119 loop {
121 while self.paused {
122 if let Some(event) = self.controller_rx.recv().await {
123 if matches!(event, Event::Resume) {
124 log::debug!("Resumed from pause");
125 self.paused = false;
126 self.send(RenderCommand::Ack);
127 self.event_stream = Some(EventStream::new());
128 continue;
129 }
130 } else {
131 error!("Event controller closed while paused.");
132 break;
133 }
134 }
135
136 while let Ok(event) = self.controller_rx.try_recv() {
138 self.handle_event(event);
140 }
141
142 self.txs.retain(|tx| !tx.is_closed());
143 if self.txs.is_empty() {
144 break;
145 }
146
147 let event = if let Some(stream) = &mut self.event_stream {
148 stream.next()
149 } else {
150 bail!("No event stream (this should be unreachable)");
151 };
152
153 tokio::select! {
154 _ = interval.tick() => {
155 self.send(RenderCommand::Tick)
156 }
157
158 _ = tokio::signal::ctrl_c() => {
160 if let Some(actions) = self.binds.get(&key!(ctrl-c).into()) {
161 self.send_actions(actions);
162 } else {
163 self.send(RenderCommand::quit());
164 info!("Received ctrl-c");
165 }
166 }
167
168 Some(event) = self.controller_rx.recv() => {
169 self.handle_event(event);
170 }
171
172 maybe_event = event => {
174 match maybe_event {
175 Some(Ok(event)) => {
176 if !matches!(
177 event,
178 CrosstermEvent::Mouse(MouseEvent {
179 kind: crossterm::event::MouseEventKind::Moved,
180 ..
181 })
182 ) {
183 info!("Event {event:?}");
184 }
185 match event {
186 CrosstermEvent::Key(k) => {
187 info!("{k:?}");
188 if let Some(key) = self.combiner.transform(k) {
189 info!("{key:?}");
190 let key = KeyCombination::normalized(key);
191 if let Some(actions) = self.binds.get(&key.into()) {
192 self.send_actions(actions);
193 } else if let Some(c) = key_code_as_letter(key) {
194 self.send(RenderCommand::Action(Action::Input(c)));
195 } else {
196 match key {
198 key!(ctrl-c) | key!(esc) => self.send(RenderCommand::quit()),
199 key!(up) => self.send_action(Action::Up(Count(1))),
200 key!(down) => self.send_action(Action::Down(Count(1))),
201 key!(enter) => self.send_action(Action::Accept),
202 key!(right) => self.send_action(Action::ForwardChar),
203 key!(left) => self.send_action(Action::BackwardChar),
204 key!(ctrl-right) => self.send_action(Action::ForwardWord),
205 key!(ctrl-left) => self.send_action(Action::BackwardWord),
206 key!(backspace) => self.send_action(Action::DeleteChar),
207 key!(ctrl-h) => self.send_action(Action::DeleteWord),
208 key!(ctrl-u) => self.send_action(Action::Cancel),
209 key!(alt-h) => self.send_action(Action::Help("".to_string())),
210 _ => {}
211 }
212 }
213 }
214 }
215 CrosstermEvent::Mouse(mouse) => {
216 if let Some(actions) = self.binds.get(&mouse.into()) {
217 self.send_actions(actions);
218 }
219 }
220 CrosstermEvent::Resize(width, height) => {
221 self.send(RenderCommand::Resize(Rect::new(0, 0, width, height)));
222 }
223 #[allow(unused_variables)]
228 CrosstermEvent::Paste(content) => {
229 #[cfg(feature = "bracketed-paste")]
230 {
231 self.send(RenderCommand::Paste(content));
232 }
233 #[cfg(not(feature = "bracketed-paste"))]
234 {
235 unreachable!()
236 }
245 }
246 _ => {},
247 }
248 }
249 Some(Err(e)) => warn!("Failed to read crossterm event: {e}"),
250 None => {
251 warn!("Reader closed");
252 break
253 }
254 }
255 }
256 }
257 }
258 Ok(())
259 }
260
261 fn send(&self, action: RenderCommand<A>) {
262 for tx in &self.txs {
263 tx.send(action.clone())
264 .unwrap_or_else(|_| debug!("Failed to send {action}"));
265 }
266 }
267
268 fn send_actions<'a>(&self, actions: impl IntoIterator<Item = &'a Action<A>>) {
269 for action in actions {
270 self.send(action.into());
271 }
272 }
273
274 pub fn print_key(&self, key_combination: KeyCombination) -> String {
275 self.fmt.to_string(key_combination)
276 }
277
278 fn send_action(&self, action: Action<A>) {
279 self.send(RenderCommand::Action(action));
280 }
281}
282
283fn key_code_as_letter(key: KeyCombination) -> Option<char> {
284 match key {
285 KeyCombination {
286 codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
287 modifiers: KeyModifiers::NONE,
288 } => Some(l),
289 KeyCombination {
290 codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
291 modifiers: KeyModifiers::SHIFT,
292 } => Some(l.to_ascii_uppercase()),
293 _ => None,
294 }
295}