main.rs

Putting it all together, we have the main.rs function:

/// Application.
pub mod app;

/// Terminal events handler.
pub mod event;

/// Widget renderer.
pub mod ui;

/// Terminal user interface.
pub mod tui;

/// Application updater.
pub mod update;

use anyhow::Result;
use app::App;
use event::{Event, EventHandler};
use ratatui::{backend::CrosstermBackend, Terminal};
use tui::Tui;
use update::update;

fn main() -> Result<()> {
  // Create an application.
  let mut app = App::new();

  // Initialize the terminal user interface.
  let backend = CrosstermBackend::new(std::io::stderr());
  let terminal = Terminal::new(backend)?;
  let events = EventHandler::new(250);
  let mut tui = Tui::new(terminal, events);
  tui.enter()?;

  // Start the main loop.
  while !app.should_quit {
    // Render the user interface.
    tui.draw(&mut app)?;
    // Handle events.
    match tui.events.next()? {
      Event::Tick => {},
      Event::Key(key_event) => update(&mut app, key_event),
      Event::Mouse(_) => {},
      Event::Resize(_, _) => {},
    };
  }

  // Exit the user interface.
  tui.exit()?;
  Ok(())
}

Because we call tui.events.next() in a loop, it blocks until there’s an event generated. If there’s a key press, the state updates and the UI is refreshed. If there’s no key press, a Tick event is generated every 250 milliseconds, which causes the UI to be refreshed.

This is what it looks like in practice to:

  • Run the TUI
  • Wait 2.5 seconds
  • Press j 5 times
  • Wait 2.5 seconds
  • Press k 5 times
  • Wait 2.5 seconds
  • Press q

Counter app demo

You can find the full source code for this multiple files tutorial here: https://github.com/ratatui-org/ratatui-book/tree/main/src/tutorial/counter-app/ratatui-counter-app.

Question

Right now, this TUI application will render every time a key is pressed. As an exercise, can you make this app render only an a predefined tick rate?