use super::Event;
use crossterm::event::EventStream;
use futures::StreamExt;
use std::time::Duration;
use tokio::sync::mpsc;
pub struct EventLoop {
tick_rate: Duration,
custom_rx: mpsc::UnboundedReceiver<Event>,
custom_tx: mpsc::UnboundedSender<Event>,
}
impl EventLoop {
pub fn new(tick_rate: Duration) -> Self {
let (custom_tx, custom_rx) = mpsc::unbounded_channel();
Self {
tick_rate,
custom_rx,
custom_tx,
}
}
pub fn default_rate() -> Self {
Self::new(Duration::from_millis(100))
}
pub fn sender(&self) -> mpsc::UnboundedSender<Event> {
self.custom_tx.clone()
}
pub async fn next(&mut self) -> Option<Event> {
let tick_delay = tokio::time::sleep(self.tick_rate);
tokio::pin!(tick_delay);
let mut event_stream = EventStream::new();
tokio::select! {
maybe_event = event_stream.next() => {
match maybe_event {
Some(Ok(event)) => Some(event.into()),
Some(Err(_)) => None,
None => None,
}
}
Some(event) = self.custom_rx.recv() => {
Some(event)
}
_ = &mut tick_delay => {
Some(Event::Tick)
}
}
}
pub async fn run<F>(&mut self, mut handler: F)
where
F: FnMut(Event) -> bool,
{
#[allow(clippy::while_let_loop)]
loop {
if let Some(event) = self.next().await {
if !handler(event) {
break;
}
} else {
break;
}
}
}
}
#[allow(dead_code)]
pub struct EventLoopBuilder {
tick_rate: Duration,
}
#[allow(dead_code)]
impl EventLoopBuilder {
pub fn new() -> Self {
Self {
tick_rate: Duration::from_millis(100),
}
}
pub fn tick_rate(mut self, rate: Duration) -> Self {
self.tick_rate = rate;
self
}
pub fn build(self) -> EventLoop {
EventLoop::new(self.tick_rate)
}
}
impl Default for EventLoopBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore = "requires terminal"]
async fn test_custom_event() {
let mut event_loop = EventLoop::new(Duration::from_secs(10)); let sender = event_loop.sender();
sender.send(Event::Tick).unwrap();
let event = tokio::time::timeout(Duration::from_millis(100), event_loop.next())
.await
.unwrap();
assert!(matches!(event, Some(Event::Tick)));
}
}