use super::messages::{InputEvent, KeyCode, KeyModifiers, MouseButton, MouseEvent};
use crossbeam_channel::Sender;
use crossterm::event::{self, Event, KeyEventKind};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread::{self, JoinHandle};
use std::time::Duration;
pub struct InputActor {
handle: Option<JoinHandle<()>>,
shutdown: Arc<AtomicBool>,
}
impl InputActor {
#[allow(clippy::missing_panics_doc)]
pub fn spawn(sender: Sender<InputEvent>, poll_timeout: Duration) -> Self {
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_clone = shutdown.clone();
let handle = thread::Builder::new()
.name("flywheel-input".to_string())
.spawn(move || {
Self::run_loop(&sender, &shutdown_clone, poll_timeout);
})
.expect("Failed to spawn input thread");
Self {
handle: Some(handle),
shutdown,
}
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Relaxed);
}
pub fn join(mut self) {
self.shutdown();
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
#[allow(clippy::collapsible_if)]
fn run_loop(sender: &Sender<InputEvent>, shutdown: &Arc<AtomicBool>, poll_timeout: Duration) {
loop {
if shutdown.load(Ordering::Relaxed) {
let _ = sender.send(InputEvent::Shutdown);
break;
}
match event::poll(poll_timeout) {
Ok(true) => {
match event::read() {
Ok(event) => {
if let Some(input_event) = Self::convert_event(event) {
if sender.send(input_event).is_err() {
break;
}
}
}
Err(e) => {
let _ = sender.send(InputEvent::Error(e.to_string()));
}
}
}
Ok(false) => {
}
Err(e) => {
let _ = sender.send(InputEvent::Error(e.to_string()));
}
}
}
}
fn convert_event(event: Event) -> Option<InputEvent> {
match event {
Event::Key(key_event) => {
if key_event.kind != KeyEventKind::Press {
return None;
}
let code = Self::convert_key_code(key_event.code)?;
let modifiers = Self::convert_modifiers(key_event.modifiers);
Some(InputEvent::Key { code, modifiers })
}
Event::Mouse(mouse_event) => Self::convert_mouse_event(mouse_event),
Event::Resize(width, height) => Some(InputEvent::Resize { width, height }),
Event::FocusGained => Some(InputEvent::FocusGained),
Event::FocusLost => Some(InputEvent::FocusLost),
Event::Paste(text) => Some(InputEvent::Paste(text)),
}
}
const fn convert_key_code(code: event::KeyCode) -> Option<KeyCode> {
Some(match code {
event::KeyCode::Char(c) => KeyCode::Char(c),
event::KeyCode::F(n) => KeyCode::F(n),
event::KeyCode::Backspace => KeyCode::Backspace,
event::KeyCode::Enter => KeyCode::Enter,
event::KeyCode::Left => KeyCode::Left,
event::KeyCode::Right => KeyCode::Right,
event::KeyCode::Up => KeyCode::Up,
event::KeyCode::Down => KeyCode::Down,
event::KeyCode::Home => KeyCode::Home,
event::KeyCode::End => KeyCode::End,
event::KeyCode::PageUp => KeyCode::PageUp,
event::KeyCode::PageDown => KeyCode::PageDown,
event::KeyCode::Tab => KeyCode::Tab,
event::KeyCode::BackTab => KeyCode::BackTab,
event::KeyCode::Delete => KeyCode::Delete,
event::KeyCode::Insert => KeyCode::Insert,
event::KeyCode::Esc => KeyCode::Esc,
event::KeyCode::Null => KeyCode::Null,
_ => return None, })
}
const fn convert_modifiers(mods: event::KeyModifiers) -> KeyModifiers {
KeyModifiers {
shift: mods.contains(event::KeyModifiers::SHIFT),
control: mods.contains(event::KeyModifiers::CONTROL),
alt: mods.contains(event::KeyModifiers::ALT),
super_key: mods.contains(event::KeyModifiers::SUPER),
}
}
const fn convert_mouse_event(mouse: event::MouseEvent) -> Option<InputEvent> {
let modifiers = Self::convert_modifiers(mouse.modifiers);
match mouse.kind {
event::MouseEventKind::Down(button) => {
let button = Self::convert_mouse_button(button);
Some(InputEvent::MouseDown(MouseEvent {
x: mouse.column,
y: mouse.row,
button: Some(button),
modifiers,
}))
}
event::MouseEventKind::Up(button) => {
let button = Self::convert_mouse_button(button);
Some(InputEvent::MouseUp(MouseEvent {
x: mouse.column,
y: mouse.row,
button: Some(button),
modifiers,
}))
}
event::MouseEventKind::Moved => Some(InputEvent::MouseMove(MouseEvent {
x: mouse.column,
y: mouse.row,
button: None,
modifiers,
})),
event::MouseEventKind::Drag(button) => {
let button = Self::convert_mouse_button(button);
Some(InputEvent::MouseMove(MouseEvent {
x: mouse.column,
y: mouse.row,
button: Some(button),
modifiers,
}))
}
event::MouseEventKind::ScrollUp => Some(InputEvent::MouseScroll {
x: mouse.column,
y: mouse.row,
delta: 1,
}),
event::MouseEventKind::ScrollDown => Some(InputEvent::MouseScroll {
x: mouse.column,
y: mouse.row,
delta: -1,
}),
_ => None,
}
}
const fn convert_mouse_button(button: event::MouseButton) -> MouseButton {
match button {
event::MouseButton::Left => MouseButton::Left,
event::MouseButton::Right => MouseButton::Right,
event::MouseButton::Middle => MouseButton::Middle,
}
}
}
impl Drop for InputActor {
fn drop(&mut self) {
self.shutdown();
}
}