Skip to content

Latest commit

 

History

History
184 lines (167 loc) · 6.32 KB

README.md

File metadata and controls

184 lines (167 loc) · 6.32 KB

Simple Async template

This simple async template will create the following project structure:

src/
├── app.rs     -> holds the state and application logic
├── event.rs   -> handles the terminal events (key press, mouse click, resize, etc.)
├── handler.rs -> handles the key press events and updates the application
├── lib.rs     -> module definitions
├── main.rs    -> entry-point
├── tui.rs     -> initializes/exits the terminal interface
└── ui.rs      -> renders the widgets / UI

This is identical to the [simple] template but has async events out of the box with tokio and crossterm's EventStream.

simple

Here's a diff if you use as reference if want to convert your own code to async:

./Cargo.toml

--- ./simple/Cargo.toml	2023-12-15 11:45:40
    ./simple-async/Cargo.toml	2024-01-14 05:33:25
@@ -6,5  6,8 @@
 edition = "2021"

 [dependencies]
-crossterm = "0.27.0"
-ratatui = "0.24.0"
 crossterm = { version = "0.27.0", features = ["event-stream"] }
 futures = "0.3.30"
 ratatui = "0.25.0"
 tokio = { version = "1.35.1", features = ["full"] }

./src/event.rs

--- ./simple/src/event.rs	2024-01-06 22:25:37
    ./simple-async/src/event.rs	2024-01-14 05:42:04
@@ -1,8  1,10 @@
 use std::time::Duration;
 
 use crossterm::event::{Event as CrosstermEvent, KeyEvent, MouseEvent};
 use futures::{FutureExt, StreamExt};
 use tokio::sync::mpsc;
 
 use crate::app::AppResult;
-use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
-use std::sync::mpsc;
-use std::thread;
-use std::time::{Duration, Instant};

 /// Terminal events.
 #[derive(Clone, Copy, Debug)]
@@ -22,46  24,53 @@
 #[derive(Debug)]
 pub struct EventHandler {
     /// Event sender channel.
-    sender: mpsc::Sender<Event>,
     sender: mpsc::UnboundedSender<Event>,
     /// Event receiver channel.
-    receiver: mpsc::Receiver<Event>,
     receiver: mpsc::UnboundedReceiver<Event>,
     /// Event handler thread.
-    handler: thread::JoinHandle<()>,
     handler: tokio::task::JoinHandle<()>,
 }

 impl EventHandler {
     /// Constructs a new instance of [`EventHandler`].
     pub fn new(tick_rate: u64) -> Self {
         let tick_rate = Duration::from_millis(tick_rate);
-        let (sender, receiver) = mpsc::channel();
-        let handler = {
-            let sender = sender.clone();
-            thread::spawn(move || {
-                let mut last_tick = Instant::now();
         let (sender, receiver) = mpsc::unbounded_channel();
         let _sender = sender.clone();
         let handler = tokio::spawn(async move {
             let mut reader = crossterm::event::EventStream::new();
             let mut tick = tokio::time::interval(tick_rate);
                 loop {
-                    let timeout = tick_rate
-                        .checked_sub(last_tick.elapsed())
-                        .unwrap_or(tick_rate);
-
-                    if event::poll(timeout).expect("failed to poll new events") {
-                        match event::read().expect("unable to read event") {
-                            CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
-                            CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
-                            CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
-                            CrosstermEvent::FocusGained => Ok(()),
-                            CrosstermEvent::FocusLost => Ok(()),
-                            CrosstermEvent::Paste(_) => unimplemented!(),
                 let tick_delay = tick.tick();
                 let crossterm_event = reader.next().fuse();
                 tokio::select! {
                   _ = tick_delay => {
                     _sender.send(Event::Tick).unwrap();
                         }
-                        .expect("failed to send terminal event")
                   Some(Ok(evt)) = crossterm_event => {
                     match evt {
                       CrosstermEvent::Key(key) => {
                         if key.kind == crossterm::event::KeyEventKind::Press {
                           _sender.send(Event::Key(key)).unwrap();
                     }
-
-                    if last_tick.elapsed() >= tick_rate {
-                        sender.send(Event::Tick).expect("failed to send tick event");
-                        last_tick = Instant::now();
                       },
                       CrosstermEvent::Mouse(mouse) => {
                         _sender.send(Event::Mouse(mouse)).unwrap();
                       },
                       CrosstermEvent::Resize(x, y) => {
                         _sender.send(Event::Resize(x, y)).unwrap();
                       },
                       CrosstermEvent::FocusLost => {
                       },
                       CrosstermEvent::FocusGained => {
                       },
                       CrosstermEvent::Paste(_) => {
                       },
                     }
                 }
-            })
                 };
             }
         });
         Self {
             sender,
             receiver,
@@ -73,7  82,13 @@
     ///
     /// This function will always block the current thread if
     /// there is no data available and it's possible for more data to be sent.
-    pub fn next(&self) -> AppResult<Event> {
-        Ok(self.receiver.recv()?)
     pub async fn next(&mut self) -> AppResult<Event> {
         self.receiver
             .recv()
             .await
             .ok_or(Box::new(std::io::Error::new(
                 std::io::ErrorKind::Other,
                 "This is an IO error",
             )))
     }
 }

./src/main.rs

diff -bur ./simple/src/main.rs ./simple-async/src/main.rs
--- ./simple/src/main.rs	2023-12-15 11:45:41
    ./simple-async/src/main.rs	2024-01-14 05:36:37
@@ -6,7  6,8 @@
 use ratatui::backend::CrosstermBackend;
 use ratatui::Terminal;

-fn main() -> AppResult<()> {
 #[tokio::main]
 async fn main() -> AppResult<()> {
     // Create an application.
     let mut app = App::new();

@@ -22,7  23,7 @@
         // Render the user interface.
         tui.draw(&mut app)?;
         // Handle events.
-        match tui.events.next()? {
         match tui.events.next().await? {
             Event::Tick => app.tick(),
             Event::Key(key_event) => handle_key_events(key_event, &mut app)?,
             Event::Mouse(_) => {}