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 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 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 _ = 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 }
136
137 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 }
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}