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 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 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 _ = 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 }
159
160 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 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 _ => {},
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}