1use crate::Result;
2use crate::action::{Action, ActionExt, NullActionExt};
3use crate::binds::BindMap;
4use crate::message::{Event, RenderCommand};
5use crokey::{Combiner, KeyCombination, KeyCombinationFormat, key};
6use crossterm::event::{
7 Event as CrosstermEvent, EventStream, KeyModifiers, MouseEvent, MouseEventKind,
8};
9use futures::stream::StreamExt;
10use log::{debug, error, info, warn};
11use ratatui::layout::Rect;
12use tokio::sync::mpsc;
13use tokio::time::{self};
14
15pub type RenderSender<A = NullActionExt> = mpsc::UnboundedSender<RenderCommand<A>>;
16#[derive(Debug)]
17pub struct EventLoop<A: ActionExt> {
18 txs: Vec<mpsc::UnboundedSender<RenderCommand<A>>>,
19 tick_interval: time::Duration,
20
21 pub binds: BindMap<A>,
22 combiner: Combiner,
23 fmt: KeyCombinationFormat,
24
25 mouse_events: bool,
26 paused: bool,
27 event_stream: Option<EventStream>,
28 controller_rx: mpsc::UnboundedReceiver<Event>,
29 controller_tx: mpsc::UnboundedSender<Event>,
30}
31
32impl<A: ActionExt> Default for EventLoop<A> {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl<A: ActionExt> EventLoop<A> {
39 pub fn new() -> Self {
40 let combiner = Combiner::default();
41 let fmt = KeyCombinationFormat::default();
42 let (controller_tx, controller_rx) = tokio::sync::mpsc::unbounded_channel();
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 pub fn add_tx(&mut self, handler: mpsc::UnboundedSender<RenderCommand<A>>) -> &mut Self {
72 self.txs.push(handler);
73 self
74 }
75
76 pub fn with_mouse_events(mut self) -> Self {
77 self.mouse_events = true;
78 self
79 }
80
81 pub fn clear_txs(&mut self) {
82 self.txs.clear();
83 }
84
85 pub fn get_controller(&self) -> mpsc::UnboundedSender<Event> {
86 self.controller_tx.clone()
87 }
88
89 fn handle_event(&mut self, e: Event) {
90 debug!("Received: {e}");
91
92 match e {
93 Event::Pause => {
94 self.paused = true;
95 self.send(RenderCommand::Ack);
96 self.event_stream = None; }
98 Event::Refresh => {
99 self.send(RenderCommand::Refresh);
100 }
101 _ => {}
102 }
103 if let Some(actions) = self.binds.get(&e.into()) {
104 self.send_actions(actions);
105 }
106 }
107
108 pub fn binds(&mut self, binds: BindMap<A>) -> &mut Self {
109 self.binds = binds;
110 self
111 }
112
113 pub async fn run(&mut self) -> Result<()> {
115 self.event_stream = Some(EventStream::new());
116 let mut interval = time::interval(self.tick_interval);
117
118 loop {
120 while self.paused {
122 if let Some(event) = self.controller_rx.recv().await {
123 if matches!(event, Event::Resume) {
124 debug!("Resumed from pause");
125 self.paused = false;
126 self.send(RenderCommand::Ack);
127 self.event_stream = Some(EventStream::new());
128 break;
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 continue; };
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(1)),
200 key!(down) => self.send_action(Action::Down(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 key!(ctrl-'[') => self.send_action(Action::ToggleWrap),
211 key!(ctrl-']') => self.send_action(Action::ToggleWrapPreview),
212 _ => {}
213 }
214 }
215 }
216 }
217 CrosstermEvent::Mouse(mouse) => {
218 if let Some(actions) = self.binds.get(&mouse.into()) {
219 self.send_actions(actions);
220 } else if !matches!(mouse.kind, MouseEventKind::Moved) {
221 self.send(RenderCommand::Mouse(mouse));
224 }
225 }
226 CrosstermEvent::Resize(width, height) => {
227 self.send(RenderCommand::Resize(Rect::new(0, 0, width, height)));
228 }
229 #[allow(unused_variables)]
230 CrosstermEvent::Paste(content) => {
231 #[cfg(feature = "bracketed-paste")]
232 {
233 self.send(RenderCommand::Paste(content));
234 }
235 #[cfg(not(feature = "bracketed-paste"))]
236 {
237 unreachable!()
238 }
239 }
240 _ => {},
245 }
246 }
247 Some(Err(e)) => warn!("Failed to read crossterm event: {e}"),
248 None => {
249 warn!("Reader closed");
250 break
251 }
252 }
253 }
254 }
255 }
256 Ok(())
257 }
258
259 fn send(&self, action: RenderCommand<A>) {
260 for tx in &self.txs {
261 tx.send(action.clone())
262 .unwrap_or_else(|_| debug!("Failed to send {action}"));
263 }
264 }
265
266 fn send_actions<'a>(&self, actions: impl IntoIterator<Item = &'a Action<A>>) {
267 for action in actions {
268 self.send(action.into());
269 }
270 }
271
272 pub fn print_key(&self, key_combination: KeyCombination) -> String {
273 self.fmt.to_string(key_combination)
274 }
275
276 fn send_action(&self, action: Action<A>) {
277 self.send(RenderCommand::Action(action));
278 }
279}
280
281fn key_code_as_letter(key: KeyCombination) -> Option<char> {
282 match key {
283 KeyCombination {
284 codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
285 modifiers: KeyModifiers::NONE,
286 } => Some(l),
287 KeyCombination {
288 codes: crokey::OneToThree::One(crossterm::event::KeyCode::Char(l)),
289 modifiers: KeyModifiers::SHIFT,
290 } => Some(l.to_ascii_uppercase()),
291 _ => None,
292 }
293}