Trying Ratatui TUI: Rust Text-based User Interface Apps
Tags: rust, gamedev, tui
Published: 2024-06-12 16:42:23

---
title: "Trying Ratatui TUI: Rust Text-based User Interface Apps"
published: "true"
description: "Trying Ratatui TUI 🧑🏽‍🍳 building a text-based UI number game in the Terminal 🖥️ in Rust with Ratatui immediate mode rendering."
tags: "rust, gamedev, tui"
canonical_url: "https://rodneylab.com/trying-ratatui-tui/"
cover_image: "https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y2spcub5vyvd8gxr3r3c.png"
---
## Ratatui and Text-based User Interfaces

I have been trying Ratatui TUI, a Rust crate for building Text-based User Interface (**TUI**) apps, which typically run in the Terminal.  Ratatui, along with Go-based Bubble Tea might be responsible for the recent growth in TUIs.  Some examples of TUI apps are **Lazygit**, glow, **Soft Serve**, **ATAC** and rlt.  C++ programmers are not left out — FTXUI is a modern C++ library for building TUIs.

You use the Ratatui crate to build command line apps in Rust, though it is a little different to other Rust libraries like clap and inquire.  **clap** focusses on managing and parsing command line flags (its name is an acronym: Command Line Argument Parser).  **inquire**, is designed for interacting with the user, prompting them for information via the command line.  Ratatui is different to both, and gives you more control over the text-based app&rsquo;s user interface layout and handling interaction.

## 🧑🏽‍🎓 Ratatui Learning Resources

Ratatui is a powerful library, so there is a little to learn, and the project provides three beginner tutorials to help flatten the learning curve.  These are:

- <a href="https://ratatui.rs/tutorials/hello-world/">Ratatui Hello World Tutorial</a>, which is a good starting point if you are quite new to the Rust ecosystem;
- <a href="https://ratatui.rs/tutorials/counter-app/">Counter App</a>, which takes things up a level and introduces immediate mode rendering; and 
- <a aria-label="J S O N editor" href="https://ratatui.rs/tutorials/json-editor/">JSON Editor</a>, which introduces more sophisticated layouts.

If this is your first Rust app, you might want to start with either <a href="https://rust-cli.github.io/book/index.html">Command line app in Rust</a>, or probably <a href="https://doc.rust-lang.org/stable/book/">The Rust Programming Language</a>, both free web books.

## 🧱 What I Built

After following the tutorials, a nice extension was to create a basic numbers game, inspired by the <a href="https://en.wikipedia.org/wiki/Countdown_(game_show)#Numbers_Round">UK TV Countdown quiz</a>.  For this arithmetic challenge, the player randomly picks six numbers, then using +, -, * and / operators, tries to arrive at a target.

This little game provided a natural extension to the JSON editor tutorial, mentioned above.

![Ratatui for Terminal Fireworks: Screen capture shows game running in the Terminal.  The main title reads “How did you do?”.  Below, text reads You nailed it. “You hit the target!”, and below that, taking up more than half the screen, are a number of colourful dots in the shape of a recently ignited firework.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b52a73mkga9mlvd4jyo0.png)

## ⚙️ Project setup

Beyond the `ratatui` and `crossbeam` crates used in the JSON tutorial, I added `num_parser` and `rand`.  Naturally, `rand` was useful for generating the target and helping select the six, initial numbers.

```toml
[package]
name = "countdown-numbers"
version = "0.1.0"
edition = "2021"
license = "BSD-3-Clause"
repository = "https://github.com/rodneylab/countdown-numbers"
rust-version = "1.74"
description = "Trying Ratatui TUI 🧑🏽‍🍳 building a text-based UI number game in the Terminal 🖥️ in Rust with Ratatui immediate mode rendering."
 
[dependencies]
crossterm = "0.27.0"
num_parser = "1.0.2"
rand = "0.8.5"
ratatui = "0.26.3"
```

I set the game up, so the player typed in their solution attempt, with their chosen numbers, using familiar algebraic notation.  For example, let&rsquo;s say the target is `326` and the six numbers are `1`, `2`, `2`, `75`, `50` and `25`.  Here, it is possible to reach the target exactly, and the player could enter their solution into the Ratatui app as:

```plaintext
(2 * 2 * 75) + 25 + 1
```

The `num_parser` crate can parse that expression and yield the result (`326`), significantly reducing the number of lines of code I had to write.

That was all I needed for dependencies: `crossterm`, `num_parser`, `rand` and `ratatui`.

## 🧑🏽‍🍳 What I Liked about Ratatui

### 📚 Fantastic Docs

The three tutorials are very well written, and provide a great introduction to using Ratatui, including unit testing your app.  Beyond the tutorials, I found the <a href="https://docs.rs/ratatui/latest/ratatui/">official Ratatui API docs</a> were detailed enough to solve all the queries I had as I built the app.

### 📦 Leveraging crates.io

Being able to leverage the Rust crate ecosystem was a huge help.  I mentioned `num_parser` did a lot of heavy-lifting.  This was quick to <a href="https://crates.io/crates/num_parser">find `num_parser` using crates.io</a>.  Although a similar C++ library might exist, I don&rsquo;t think I could have found it so easily.  I am not familiar enough with the Go ecosystem to comment on how that would have worked out for me, drop a comment below if you are familiar, or if you are a C++ aficionado, and know an easy way to find C++ libraries.

### 🖥️ Trying Ratatui TUI: Immediate Mode

I kept the app structure from the JSON tutorial, that is, with three main source files:

- `src/main.rs`: contains the main loop, draws the UI and listens for UI events, immediately calling app methods;
- `src/app.rs`: manages app state and provides helper functions the app calls on user event; and
- `src/ui.rs`: generates the UI based on the current app state.

The immediate mode pattern worked well, writing how the app UI should look for any given state, with Rust pattern matching helping me get going quicker, making sure I covered all possible states, with no surprises.

![Ratatui for Terminal Fireworks: Canvas examples screen capture show a low-resolution world map in the left half of the terminal, outlined with green dots on a black background.  The right half of the screen shows a further two demos: a pong demo up top, and a collection of rectangles, resembling a histogram at the bottom.  The pong example has a large, yellow ball close to the middle of the screen, while the other demo features red and purple rectangles.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f4tfl22zj3deoxre71f0.png)

As an example of how this worked, we might look at the initial number picking.  The player presses <kbd>[</kbd> to pick a small number and <kbd>]</kbd> for a large one.  These key presses are captured in event listener code in <code>src/main.rs</code>:

```rust
match app.current_screen {
    CurrentScreen::Introduction => {
        if key.code == KeyCode::Enter {
            app.current_screen = CurrentScreen::PickingNumbers;
        }
    }
    CurrentScreen::PickingNumbers => match key.code {
        KeyCode::Enter => {
            if app.is_number_selection_complete() {
                app.current_screen = CurrentScreen::Playing;
            }
        }
        KeyCode::Char(']') => {
            app.pick_random_large_number();
        }
        KeyCode::Char('[') => {
            app.pick_random_small_number();
        }
        _ => {}
    },
    // TRUNCATED...
}

```

So pressing those square bracket keys triggers an app state update (`src/app.rs`):

```rust
pub fn pick_random_large_number(&mut self) {
    if let Some(index_value) = self.random_available_large_number_index() {
        let result = self.available_large_numbers[index_value];
        let picked_index = self.selected_numbers.iter().position(|&val| val.is_none());
        if let Some(picked_index_value) = picked_index {
            if result.is_some() {
                self.selected_numbers[picked_index_value] = result;
                self.available_large_numbers[index_value] = None;
            };
        }
    }
}
```

Then the UI model is updated (`src/ui.rs`):

```rust
fn create_selected_numbers_block(app: &App) -> Paragraph {
    let mut selected_numbers_text = app.selected_numbers.into_iter().fold(
        vec![Span::styled("Numbers: ", Style::default())],
        |mut accum, val| {
            if let Some(value) = val {
                accum.push(Span::styled(
                    format!("{value} "),
                    Style::default().fg(Color::Green),
                ));
            } else {
                accum.push(Span::styled("_ ", Style::default().fg(Color::Green)));
            };
            accum
        },
    );
    // TRUNCATED...
}
```

Hence, the current app state gets reflected on the next enumeration of the event loop.

There is a link further down to the complete code for the project.

## 🏁 Trying Ratatui TUI: What Next?

So far, I have a minimal proof of concept.  I would like to remove some rough edges.  In particular:

- add some fireworks to make the victory screen feel a little more of a celebration;
- add sound feedback when the user hits keys; and
- improve gameplay.

For improving gameplay, there are already different messages depending on how close to the target you got.  However, I would like to take this to the next level, adding a game timer and putting best times on a high-score board.  This definitely provides motivation for me playing <a href="https://apps.apple.com/gb/app/sudoku-pi/id6467504425">Sudoku Pi (also written in Rust)</a>, and I hope it will translate to this game.

## 🙌🏽 Trying Ratatui TUI: Wrapping Up

In this post on trying Ratatui TUI, we looked got an introduction to immediate mode user interfaces in Rust.  In particular, we saw:

- TUI **alternatives to Rust&rsquo;s Ratatui** in Go and C++;
- other **Rust Terminal app tooling** and how Ratatui is different; and
- some **learning resources for getting started with Ratatui**.

I hope you found this useful.  As promised, you can <a href="https://github.com/rodneylab/countdown-numbers">get the full project code on the Rodney Lab GitHub repo</a>.  I would love to hear from you, if you are also new to Rust game development.  Do you have alternative resources you found useful? How will you use this code in your own projects?

## 🙏🏽 Trying Ratatui TUI: Feedback

If you have found this post useful, see links below for further related content on this site.  Let me know if there are any ways I can improve on it. I hope you will use the code or starter in your own projects. Be sure to share your work on X, giving me a mention, so I can see what you did. Finally, be sure to let me know ideas for other short videos you would like to see. Read on to find ways to get in touch, further below. If you have found this post useful, even though you can only afford even a tiny contribution, please <a aria-label="Support Rodney Lab via Buy me a Coffee" href="https://rodneylab.com/giving/">consider supporting me through Buy me a Coffee</a>.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via <a href="https://twitter.com/messages/compose?recipient_id=1323579817258831875">@askRodney</a> on X (previously Twitter) and also, join the <a href="https://matrix.to/#/%23rodney:matrix.org">#rodney</a> Element Matrix room. Also, see <a aria-label="Get in touch with Rodney Lab" href="https://rodneylab.com/contact/">further ways to get in touch with Rodney Lab</a>. I post regularly on <a href="https://rodneylab.com/tags/gaming/">Game Dev</a> as well as <a href="https://rodneylab.com/tags/rust/">Rust</a> and <a href="https://rodneylab.com/tags/c++/">C++</a> (among other topics). Also, <a aria-label="Subscribe to the   Rodney Lab newsletter" href="https://newsletter.rodneylab.com/issue/latest-issue">subscribe to the newsletter to keep up-to-date</a> with our latest projects.