2024-10-27 00:37:13 +02:00

278 lines
9.1 KiB
Rust

use std::{
fmt::Display,
future::Future,
pin::Pin,
task::{Context, Poll as TaskPoll},
time::Duration,
};
use crossterm::event::{
KeyCode::{
BackTab, Backspace, Char, Delete, Down, End, Enter, Esc, Home, Insert,
Left, PageDown, PageUp, Right, Tab, Up, F,
},
KeyEvent, KeyModifiers,
};
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use tracing::{debug, warn};
#[derive(Debug, Clone, Copy)]
pub enum Event<I> {
Closed,
Input(I),
FocusLost,
FocusGained,
Resize(u16, u16),
Tick,
}
#[derive(
Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Hash,
)]
pub enum Key {
Backspace,
Enter,
Left,
Right,
Up,
Down,
CtrlSpace,
CtrlBackspace,
CtrlEnter,
CtrlLeft,
CtrlRight,
CtrlUp,
CtrlDown,
CtrlDelete,
AltSpace,
AltEnter,
AltBackspace,
AltDelete,
AltUp,
AltDown,
AltLeft,
AltRight,
Home,
End,
PageUp,
PageDown,
BackTab,
Delete,
Insert,
F(u8),
Char(char),
Alt(char),
Ctrl(char),
Null,
Esc,
Tab,
}
impl Display for Key {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Key::Backspace => write!(f, "Backspace"),
Key::Enter => write!(f, "Enter"),
Key::Left => write!(f, ""),
Key::Right => write!(f, ""),
Key::Up => write!(f, ""),
Key::Down => write!(f, ""),
Key::CtrlSpace => write!(f, "Ctrl-Space"),
Key::CtrlBackspace => write!(f, "Ctrl-Backspace"),
Key::CtrlEnter => write!(f, "Ctrl-Enter"),
Key::CtrlLeft => write!(f, "Ctrl-←"),
Key::CtrlRight => write!(f, "Ctrl-→"),
Key::CtrlUp => write!(f, "Ctrl-↑"),
Key::CtrlDown => write!(f, "Ctrl-↓"),
Key::CtrlDelete => write!(f, "Ctrl-Del"),
Key::AltSpace => write!(f, "Alt+Space"),
Key::AltEnter => write!(f, "Alt+Enter"),
Key::AltBackspace => write!(f, "Alt+Backspace"),
Key::AltDelete => write!(f, "Alt+Delete"),
Key::AltUp => write!(f, "Alt↑"),
Key::AltDown => write!(f, "Alt↓"),
Key::AltLeft => write!(f, "Alt←"),
Key::AltRight => write!(f, "Alt→"),
Key::Home => write!(f, "Home"),
Key::End => write!(f, "End"),
Key::PageUp => write!(f, "PageUp"),
Key::PageDown => write!(f, "PageDown"),
Key::BackTab => write!(f, "BackTab"),
Key::Delete => write!(f, "Delete"),
Key::Insert => write!(f, "Insert"),
Key::F(k) => write!(f, "F{k}"),
Key::Char(c) => write!(f, "{c}"),
Key::Alt(c) => write!(f, "Alt+{c}"),
Key::Ctrl(c) => write!(f, "Ctrl-{c}"),
Key::Null => write!(f, "Null"),
Key::Esc => write!(f, "Esc"),
Key::Tab => write!(f, "Tab"),
}
}
}
#[allow(clippy::module_name_repetitions)]
pub struct EventLoop {
pub rx: mpsc::UnboundedReceiver<Event<Key>>,
//tx: mpsc::UnboundedSender<Event<Key>>,
pub abort_tx: mpsc::UnboundedSender<()>,
//tick_rate: std::time::Duration,
}
struct PollFuture {
timeout: Duration,
}
impl Future for PollFuture {
type Output = bool;
fn poll(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> TaskPoll<Self::Output> {
// Polling crossterm::event::poll, which is a blocking call
// Spawn it in a separate task, to avoid blocking async runtime
match crossterm::event::poll(self.timeout) {
Ok(true) => TaskPoll::Ready(true),
Ok(false) => {
// Register the task to be polled again after a delay to avoid busy-looping
cx.waker().wake_by_ref();
TaskPoll::Pending
}
Err(_) => TaskPoll::Ready(false),
}
}
}
async fn poll_event(timeout: Duration) -> bool {
PollFuture { timeout }.await
}
impl EventLoop {
pub fn new(tick_rate: f64, init: bool) -> Self {
let (tx, rx) = mpsc::unbounded_channel();
let tx_c = tx.clone();
let tick_interval =
Duration::from_secs_f64(1.0 / tick_rate);
let (abort, mut abort_recv) = mpsc::unbounded_channel();
if init {
//let mut reader = crossterm::event::EventStream::new();
tokio::spawn(async move {
loop {
//let event = reader.next();
let delay = tokio::time::sleep(tick_interval);
let event_available = poll_event(tick_interval);
tokio::select! {
// if we receive a message on the abort channel, stop the event loop
_ = abort_recv.recv() => {
tx_c.send(Event::Closed).unwrap_or_else(|_| warn!("Unable to send Closed event"));
tx_c.send(Event::Tick).unwrap_or_else(|_| warn!("Unable to send Tick event"));
break;
},
// if `delay` completes, pass to the next event "frame"
() = delay => {
tx_c.send(Event::Tick).unwrap_or_else(|_| warn!("Unable to send Tick event"));
},
// if the receiver dropped the channel, stop the event loop
() = tx_c.closed() => break,
// if an event was received, process it
_ = event_available => {
let maybe_event = crossterm::event::read();
match maybe_event {
Ok(crossterm::event::Event::Key(key)) => {
let key = convert_raw_event_to_key(key);
tx_c.send(Event::Input(key)).unwrap_or_else(|_| warn!("Unable to send {:?} event", key));
},
Ok(crossterm::event::Event::FocusLost) => {
tx_c.send(Event::FocusLost).unwrap_or_else(|_| warn!("Unable to send FocusLost event"));
},
Ok(crossterm::event::Event::FocusGained) => {
tx_c.send(Event::FocusGained).unwrap_or_else(|_| warn!("Unable to send FocusGained event"));
},
Ok(crossterm::event::Event::Resize(x, y)) => {
tx_c.send(Event::Resize(x, y)).unwrap_or_else(|_| warn!("Unable to send Resize event"));
},
_ => {}
}
}
}
}
});
}
Self {
//tx,
rx,
//tick_rate,
abort_tx: abort,
}
}
}
pub fn convert_raw_event_to_key(event: KeyEvent) -> Key {
debug!("Raw event: {:?}", event);
match event.code {
Backspace => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlBackspace,
KeyModifiers::ALT => Key::AltBackspace,
_ => Key::Backspace,
},
Delete => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlDelete,
KeyModifiers::ALT => Key::AltDelete,
_ => Key::Delete,
},
Enter => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlEnter,
KeyModifiers::ALT => Key::AltEnter,
_ => Key::Enter,
},
Up => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlUp,
KeyModifiers::ALT => Key::AltUp,
_ => Key::Up,
},
Down => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlDown,
KeyModifiers::ALT => Key::AltDown,
_ => Key::Down,
},
Left => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlLeft,
KeyModifiers::ALT => Key::AltLeft,
_ => Key::Left,
},
Right => match event.modifiers {
KeyModifiers::CONTROL => Key::CtrlRight,
KeyModifiers::ALT => Key::AltRight,
_ => Key::Right,
},
Home => Key::Home,
End => Key::End,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
Tab => Key::Tab,
BackTab => Key::BackTab,
Insert => Key::Insert,
F(k) => Key::F(k),
Esc => Key::Esc,
Char(' ') => match event.modifiers {
KeyModifiers::NONE | KeyModifiers::SHIFT => Key::Char(' '),
KeyModifiers::CONTROL => Key::CtrlSpace,
KeyModifiers::ALT => Key::AltSpace,
_ => Key::Null,
},
Char(c) => match event.modifiers {
KeyModifiers::NONE | KeyModifiers::SHIFT => Key::Char(c),
KeyModifiers::CONTROL => Key::Ctrl(c),
KeyModifiers::ALT => Key::Alt(c),
_ => Key::Null,
},
_ => Key::Null,
}
}