Skip to content

Instantly share code, notes, and snippets.

@sheepla
Last active March 10, 2025 12:03
Show Gist options
  • Save sheepla/7ae58877a71723b8cfdae440cdd55cf6 to your computer and use it in GitHub Desktop.
Save sheepla/7ae58877a71723b8cfdae440cdd55cf6 to your computer and use it in GitHub Desktop.
Asynchronous event stream with tokio_stream for ratatui crossterm backend, replacement for event.rs on ratatui-org/templates simple-async template
/// A module for emitting [`Event`] defined in this module by internally looping events and handling crossterm events
use crossterm::event::{Event as CrosstermEvent, EventStream as CrosstermEventStream};
use pin_project::pin_project;
use std::time::Duration;
use tokio::sync::mpsc;
use tokio_stream::{Stream, StreamExt};
#[derive(Debug, Clone, Copy)]
pub enum Event {
Tick,
Key(crossterm::event::KeyEvent),
Mouse(crossterm::event::MouseEvent),
Resize(u16, u16),
}
#[derive(Debug)]
#[pin_project]
pub struct EventStream {
#[pin]
receiver: tokio::sync::mpsc::UnboundedReceiver<Event>,
}
impl EventStream {
pub fn new(tick_rate: Duration) -> Self {
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
tokio::spawn(Self::event_loop(sender, tick_rate));
Self { receiver }
}
async fn event_loop(sender: mpsc::UnboundedSender<Event>, tick_rate: Duration) {
let mut reader = CrosstermEventStream::new();
let mut tick_interval = tokio::time::interval(tick_rate);
loop {
tokio::select! {
_ = tick_interval.tick() => {
if sender.send(Event::Tick).is_err() {
break;
}
},
maybe_event = reader.next() => {
if let Some(Ok(evt)) = maybe_event {
if let Some(event) = Self::handle_crossterm_event(evt) {
if sender.send(event).is_err() {
break;
}
}
} else {
break; // End of stream
}
},
}
}
}
fn handle_crossterm_event(event: CrosstermEvent) -> Option<Event> {
match event {
CrosstermEvent::Key(key) => Some(Event::Key(key)),
CrosstermEvent::Mouse(mouse) => Some(Event::Mouse(mouse)),
CrosstermEvent::Resize(x, y) => Some(Event::Resize(x, y)),
_ => None,
}
}
}
impl Stream for EventStream {
type Item = Event;
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let mut this = self.project();
this.receiver.poll_recv(cx)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment