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, 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 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 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 while let Ok(event) = self.controller_rx.try_recv() {
139 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 _ = 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 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 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 _ => {},
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}