v0.24.0

⚠️ We created a breaking changes document for easily going through the breaking changes in each version.

Ratatui Website/Book 📚

The site you are browsing right now (ratatui.rs) is the new homepage of ratatui! For now, we host the book here which contains a bunch of useful tutorials, concepts and FAQ sections and there is a plan to create a landing page pretty soon!

All the code is available at https://github.com/ratatui-org/ratatui-book


Frame: no more generics 🚫

If you were using the Frame type with a Backend parameter before:

fn draw<B: Backend>(frame: &mut Frame<B>) {
    // ...
}

You no longer need to provide a generic over backend (B):

fn draw(frame: &mut Frame) {
    // ...
}

New Demo / Examples ✨

We have a new kick-ass demo!

demo2

To try it out:

cargo run --example=demo2 --features="crossterm widget-calendar"

The code is available here.

We also have a new example demonstrating how to create a custom widget.

custom widget

cargo run --example=custom_widget --features=crossterm

The code is available here.

Lastly, we added an example to demonstrate RGB color options:

RGB colors

cargo run --example=colors_rgb --features=crossterm

The code is available here.


Window Size API 🪟

A new method called window_size is added for retrieving the window size. It returns a struct called WindowSize that contains both pixels (width, height) and columns/rows information.

let stdout = std::io::stdout();
let mut backend = CrosstermBackend::new(stdout);
println!("{:#?}", backend.window_size()?;

Outputs:

WindowSize {
    columns_rows: Size {
        width: 240,
        height: 30,
    },
    pixels: Size {
        width: 0,
        height: 0,
    },
}

With this new API, the goal is to improve image handling in terminal emulators by sharing terminal size and layout information, enabling precise image placement and resizing within rectangles.

See the pull request for more information: https://github.com/ratatui-org/ratatui/pull/276


BarChart: Render smol charts 📊

We had a bug where the BarChart widget doesn’t render labels that are full width. Now this is fixed and we are able to render charts smaller than 3 lines!

For example, here is how BarChart is rendered and resized from a single line to 4 lines in order:

  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8


  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8
a b c d e f g h i

  ▁ ▂ ▃ ▄ ▅ ▆ ▇ 8
a b c d e f g h i
      Group

          ▂ ▄ ▆ █
  ▂ ▄ ▆ 4 5 6 7 8
a b c d e f g h i
      Group

If you set bar_width(2) for 3 lines, then it is rendered as:

          ▂ ▄ ▆ █
  ▂ ▄ ▆ 4 5 6 7 8
a b c d e f g h i
      Group

Block: custom symbols for borders 🛡️

The Block widget has a new method called border_set that can be used to specify the symbols that are going to be used for the borders.

Block::default()
    .borders(Borders::ALL)
    .border_set(border::Set {
        top_left: "1",
        top_right: "2",
        bottom_left: "3",
        bottom_right: "4",
        vertical_left: "L",
        vertical_right: "R",
        horizontal_top: "T",
        horizontal_bottom: "B",
    })

When rendered:

1TTTTTTTTTTTTT2
L             R
3BBBBBBBBBBBBB4

There are also 2 new border types added (QuadrantInside, QuadrantOutside).

See the available border types at https://docs.rs/ratatui/latest/ratatui/widgets/block/enum.BorderType.html

Also, there are breaking changes to note here:

  • BorderType::to_line_set is renamed to to_border_set
  • BorderType::line_symbols is renamed to border_symbols

Canvas: half block marker 🖼️

A new marker named HalfBlock is added to Canvas widget along with the associated HalfBlockGrid.

The idea is to use half blocks to draw a grid of “pixels” on the screen. Because we can set two colors per cell, and because terminal cells are about twice as tall as they are wide, we can draw a grid of half blocks that looks like a grid of square pixels.

Canvas::default()
    .marker(Marker::HalfBlock)
    .x_bounds([0.0, 10.0])
    .y_bounds([0.0, 10.0])
    .paint(|context| {
        context.draw(&Rectangle {
            x: 0.0,
            y: 0.0,
            width: 10.0,
            height: 10.0,
            color: Color::Red,
        });
    });

Rendered as:

█▀▀▀▀▀▀▀▀█
█        █
█        █
█        █
█        █
█        █
█        █
█        █
█        █
█▄▄▄▄▄▄▄▄█

Line: raw constructor 📝

You can simply construct Line widgets from strings using raw (similar to Span::raw and Text::raw):

let line = Line::raw("test content");

One thing to note here is that multi-line content is converted to multiple spans with the new lines removed.


Rect: is empty? 🛍️

With the newly added is_empty method, you can check if a Rect has any area or not:

assert!(!Rect::new(1, 2, 3, 4).is_empty());
assert!(Rect::new(1, 2, 0, 4).is_empty());
assert!(Rect::new(1, 2, 3, 0).is_empty());

Layout: LRU cache 📚

The layout cache now uses a LruCache with default size set to 16 entries. Previously the cache was backed by a HashMap, and was able to grow without bounds as a new entry was added for every new combination of layout parameters.

We also added a new method called init_cache for changing the cache size if necessary:

Layout::init_cache(10);

This will only have an effect if it is called prior to any calls to layout::split().


Backend: optional underline colors 🎨

Windows 7 doesn’t support the underline color attribute, so we need to make it optional. For that, we added a feature fla called underline-color and enabled it as default.

It can be disabled as follows for applications that supports Windows 7:

ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] }

Stylized strings ✨

Although the Stylize trait is already implemented for &str which extends to String, it is not implemented for String itself.

So we added an implementation of Stylize for String that returns Span<'static> which makes the following code compile just fine instead of failing with a temporary value error:

let s = format!("hello {name}!", "world").red();

This may break some code that expects to call Stylize methods on String values and then use the String value later. For example, following code will now fail to compile because the String is consumed by set_style instead of a slice being created and consumed.

let s = String::from("hello world");
let line = Line::from(vec![s.red(), s.green()]); // fails to compile

Simply clone the String to fix the compilation error:

let s = String::from("hello world");
let line = Line::from(vec![s.clone().red(), s.green()]);

Spans: RIP 💀

The Spans was deprecated and replaced with a more ergonomic Line type in 0.21.0 and now it is removed.

Long live Line!


Other 💼

  • Simplified the internal implementation of BarChart and add benchmarks
  • Add documentation to various places including Block, Gauge, Table, BarChart, etc.
  • Used modern modules syntax throughout the codebase (xxx/mod.rs -> xxx.rs)
  • Added buffer_mut method to Frame
  • Integrated Dependabot for dependency updates and bump dependencies
  • Refactored examples
  • Fixed arithmetic overflow edge cases