1use super::messages::{InputEvent, KeyCode, KeyModifiers, MouseButton, MouseEvent};
8use crossbeam_channel::Sender;
9use crossterm::event::{self, Event, KeyEventKind};
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::sync::Arc;
12use std::thread::{self, JoinHandle};
13use std::time::Duration;
14
15pub struct InputActor {
17 handle: Option<JoinHandle<()>>,
19 shutdown: Arc<AtomicBool>,
21}
22
23impl InputActor {
24 #[allow(clippy::missing_panics_doc)]
35 pub fn spawn(sender: Sender<InputEvent>, poll_timeout: Duration) -> Self {
36 let shutdown = Arc::new(AtomicBool::new(false));
37 let shutdown_clone = shutdown.clone();
38
39 let handle = thread::Builder::new()
40 .name("flywheel-input".to_string())
41 .spawn(move || {
42 Self::run_loop(&sender, &shutdown_clone, poll_timeout);
43 })
44 .expect("Failed to spawn input thread");
45
46 Self {
47 handle: Some(handle),
48 shutdown,
49 }
50 }
51
52 pub fn shutdown(&self) {
54 self.shutdown.store(true, Ordering::Relaxed);
55 }
56
57 pub fn join(mut self) {
59 self.shutdown();
60 if let Some(handle) = self.handle.take() {
61 let _ = handle.join();
62 }
63 }
64
65 #[allow(clippy::collapsible_if)]
67 fn run_loop(sender: &Sender<InputEvent>, shutdown: &Arc<AtomicBool>, poll_timeout: Duration) {
68 loop {
69 if shutdown.load(Ordering::Relaxed) {
71 let _ = sender.send(InputEvent::Shutdown);
72 break;
73 }
74
75 match event::poll(poll_timeout) {
77 Ok(true) => {
78 match event::read() {
80 Ok(event) => {
81 if let Some(input_event) = Self::convert_event(event) {
82 if sender.send(input_event).is_err() {
83 break;
85 }
86 }
87 }
88 Err(e) => {
89 let _ = sender.send(InputEvent::Error(e.to_string()));
90 }
91 }
92 }
93 Ok(false) => {
94 }
96 Err(e) => {
97 let _ = sender.send(InputEvent::Error(e.to_string()));
98 }
99 }
100 }
101 }
102
103 fn convert_event(event: Event) -> Option<InputEvent> {
105 match event {
106 Event::Key(key_event) => {
107 if key_event.kind != KeyEventKind::Press {
109 return None;
110 }
111
112 let code = Self::convert_key_code(key_event.code)?;
113 let modifiers = Self::convert_modifiers(key_event.modifiers);
114
115 Some(InputEvent::Key { code, modifiers })
116 }
117
118 Event::Mouse(mouse_event) => Self::convert_mouse_event(mouse_event),
119
120 Event::Resize(width, height) => Some(InputEvent::Resize { width, height }),
121
122 Event::FocusGained => Some(InputEvent::FocusGained),
123
124 Event::FocusLost => Some(InputEvent::FocusLost),
125
126 Event::Paste(text) => Some(InputEvent::Paste(text)),
127 }
128 }
129
130 const fn convert_key_code(code: event::KeyCode) -> Option<KeyCode> {
132 Some(match code {
133 event::KeyCode::Char(c) => KeyCode::Char(c),
134 event::KeyCode::F(n) => KeyCode::F(n),
135 event::KeyCode::Backspace => KeyCode::Backspace,
136 event::KeyCode::Enter => KeyCode::Enter,
137 event::KeyCode::Left => KeyCode::Left,
138 event::KeyCode::Right => KeyCode::Right,
139 event::KeyCode::Up => KeyCode::Up,
140 event::KeyCode::Down => KeyCode::Down,
141 event::KeyCode::Home => KeyCode::Home,
142 event::KeyCode::End => KeyCode::End,
143 event::KeyCode::PageUp => KeyCode::PageUp,
144 event::KeyCode::PageDown => KeyCode::PageDown,
145 event::KeyCode::Tab => KeyCode::Tab,
146 event::KeyCode::BackTab => KeyCode::BackTab,
147 event::KeyCode::Delete => KeyCode::Delete,
148 event::KeyCode::Insert => KeyCode::Insert,
149 event::KeyCode::Esc => KeyCode::Esc,
150 event::KeyCode::Null => KeyCode::Null,
151 _ => return None, })
153 }
154
155 const fn convert_modifiers(mods: event::KeyModifiers) -> KeyModifiers {
157 KeyModifiers {
158 shift: mods.contains(event::KeyModifiers::SHIFT),
159 control: mods.contains(event::KeyModifiers::CONTROL),
160 alt: mods.contains(event::KeyModifiers::ALT),
161 super_key: mods.contains(event::KeyModifiers::SUPER),
162 }
163 }
164
165 const fn convert_mouse_event(mouse: event::MouseEvent) -> Option<InputEvent> {
167 let modifiers = Self::convert_modifiers(mouse.modifiers);
168
169 match mouse.kind {
170 event::MouseEventKind::Down(button) => {
171 let button = Self::convert_mouse_button(button);
172 Some(InputEvent::MouseDown(MouseEvent {
173 x: mouse.column,
174 y: mouse.row,
175 button: Some(button),
176 modifiers,
177 }))
178 }
179 event::MouseEventKind::Up(button) => {
180 let button = Self::convert_mouse_button(button);
181 Some(InputEvent::MouseUp(MouseEvent {
182 x: mouse.column,
183 y: mouse.row,
184 button: Some(button),
185 modifiers,
186 }))
187 }
188 event::MouseEventKind::Moved => Some(InputEvent::MouseMove(MouseEvent {
189 x: mouse.column,
190 y: mouse.row,
191 button: None,
192 modifiers,
193 })),
194 event::MouseEventKind::Drag(button) => {
195 let button = Self::convert_mouse_button(button);
196 Some(InputEvent::MouseMove(MouseEvent {
197 x: mouse.column,
198 y: mouse.row,
199 button: Some(button),
200 modifiers,
201 }))
202 }
203 event::MouseEventKind::ScrollUp => Some(InputEvent::MouseScroll {
204 x: mouse.column,
205 y: mouse.row,
206 delta: 1,
207 }),
208 event::MouseEventKind::ScrollDown => Some(InputEvent::MouseScroll {
209 x: mouse.column,
210 y: mouse.row,
211 delta: -1,
212 }),
213 _ => None,
214 }
215 }
216
217 const fn convert_mouse_button(button: event::MouseButton) -> MouseButton {
219 match button {
220 event::MouseButton::Left => MouseButton::Left,
221 event::MouseButton::Right => MouseButton::Right,
222 event::MouseButton::Middle => MouseButton::Middle,
223 }
224 }
225}
226
227impl Drop for InputActor {
228 fn drop(&mut self) {
229 self.shutdown();
230 }
231}