Back to Ratatui

v0.29.0

src/content/docs/highlights/v0.29.md

latest12.5 KB
Original Source

We are excited to announce Ratatui 0.29.0! See the breaking changes for this release here.

Big shoutout to @dekirsu for the kickass animation above! We will start improving our website soon!

Sparkline: Empty bar style 📊

You can now distinguish between empty bars and bars with a value of 0 in the Sparkline widget.

Before:

After:

To achieve this, we added the absent_value_style and absent_value_symbol functions to the Sparkline widget.

rust
let widget = Sparkline::default()
    .absent_value_style(Style::default().fg(Color::Red)) // new!
    .absent_value_symbol(symbols::shade::FULL) // new!
    .data([
        None, // absent, will be rendered as a red full block
        Some(1),
        Some(2),
        Some(3),
        Some(4),
        Some(5),
        Some(6),
        Some(7),
        Some(8),
    ]);
let buffer = render(widget, 12);
let mut expected = Buffer::with_lines(["█▁▂▃▄▅▆▇█xxx"]);
expected.set_style(Rect::new(0, 0, 1, 1), Style::default().fg(Color::Red));
assert_eq!(buffer, expected);

:::caution

Sparkline::data takes IntoIterator<Item = SparklineBar> instead of &[u64] and is no longer const.

:::


Overlapping layouts 🔄

:::tip[TLDR]

You can use Layout::spacing(-1) to create layouts with overlapping segments.

:::

Layout::spacing is now generic and can take:

  • Zero or positive numbers, e.g. Layout::spacing(1) (current functionality)
  • Negative number, e.g. Layout::spacing(-1) (new!)
  • Variant of the Spacing (new!)
    • Spacing::Space
    • Spacing::Overlap

This allows creating layouts with a shared pixel for segments. When spacing(negative_value) is used, spacing is ignored and all segments will be adjacent and have pixels overlapping.

Here is a snippet from the implementation:

rust
let (segments, spacers) = Layout::horizontal([Length(10), Length(10), Length(10)])
    .flex(Flex::Center)
    .spacing(-1) // new feature
    .split_with_spacers(lower);

for segment in segments.iter() {
    frame.render_widget(
        crate::widgets::Block::bordered()
            .border_set(crate::symbols::border::DOUBLE),
        *segment,
    );
}
for spacer in spacers.iter() {
    frame.render_widget(crate::widgets::Block::bordered(), *spacer);
}

You can see that drawing a border on top of an existing border overwrites it:

┌─────────┐╔════════╔════════╔════════╗┌─────────┐
└─────────┘╚════════╚════════╚════════╝└─────────┘

Future versions will enhance border drawing by combining borders to handle overlaps better.


Table: Support selecting columns and cells 🗃️

You can now select columns and cells in a Table widget!

<video controls> <source src="https://github.com/user-attachments/assets/e5fd2858-4931-4ce1-a2f6-a5ea1eacbecc" type="video/mp4"> Your browser does not support the video tag. </video>

To select a column or cell, use the TableState methods select_column and select_cell. We also added scroll_right_by and scroll_left_by along with other convenience methods.

rust
let mut state = TableState::new().with_selected_column(Some(1));
state.select_first_column();
state.select_next_column();
state.select_previous_column();
state.select_last_column();
state.scroll_right_by(4);
state.scroll_left_by(20);
state.select_column(Some(1));
state.select_cell(Some((1, 5)));

The selected column and cell styles can be set using Table::column_highlight_style and Table::cell_highlight_style.

For example:

rust
let table = Table::new(rows, [Constraint::Length(5); 3])
    .highlight_symbol(">>")
    .row_highlight_style(Style::new().red())
    .column_highlight_style(Style::new().blue());

:::caution

  • The serialized output of the state will now include the selected_column field.
  • The Table::highlight_style is now deprecated in favor of Table::row_highlight_style.

:::


Tabs: Support deselection 🚫

Tabs::select() now accepts Into<Option<usize>> instead of usize. This allows tabs to be deselected by passing None.

rust
let tabs = Tabs::new(vec!["Tab 1", "Tab 2"]).select(None);

However, this breaks any code already using parameter type inference:

diff
let selected = 1u8;
- let tabs = Tabs::new(["A", "B"]).select(selected.into())
+ let tabs = Tabs::new(["A", "B"]).select(selected as usize)

Terminal: Support scrolling regions 🖥️

The current implementation of Terminal::insert_before used to cause the viewport to flicker as described in this issue.

We introduced a new crate feature called scrolling-regions to address this issue. This feature uses terminal scrolling regions to implement Terminal::insert_before without flickering.

:::note

Terminal scrolling regions (sometimes called "scroll regions") allow a terminal to have its scrolling region set to something other than the whole screen. When a scroll ANSI sequence is sent to the terminal and it has a non-default scrolling region, the terminal will scroll just inside of that region.

When the viewport takes up the entire screen, we create a scrolling region of just the top line (could be more) of the viewport, then use that to draw the lines we want to output. When we're done, we scroll it up by one line, into the scrollback history, and then redraw the top line from the viewport.

For achieving that, we added two new Backend methods: scroll_region_up and scroll_region_down. These methods are implemented on all backends in the codebase.

:::

To enable this feature for your Viewport, update your Cargo.toml as follows:

toml
[dependencies]
ratatui = { version = "0.29", features = ["scrolling-regions"] }

See the implementation for more details.


Color: HSLuv support 🎨

After enabling the palette feature, you can now use the Hsluv struct to create colors in the HSLuv color space:

rust
use ratatui::{palette::Hsluv, style::Color};

let color: Color = Color::from_hsluv(Hsluv::new(0.0, 100.0, 0.0));
assert_eq!(color, Color::Rgb(0, 0, 0));

:::note

Also, the Color::from_hsl method now accepts a palette::Hsl value instead of individual components:

diff
- Color::from_hsl(360.0, 100.0, 100.0)
+ Color::from_hsl(Hsl::new(360.0, 100.0, 100.0))

This means that you need to enable the palette feature in your Cargo.toml:

toml
[dependencies]
ratatui = { version = "0.29", features = ["palette"] }

:::


Canvas: draw example 🎨

We extended the Canvas example to include a drawing feature. You can now draw on the canvas using your mouse:


Ratatui logo widget 🖼️

We added a new widget called RatatuiLogo that can be used to render the Ratatui logo in the terminal.

rust
use ratatui::{Frame, widgets::RatatuiLogo};

fn draw(frame: &mut Frame) {
    frame.render_widget(RatatuiLogo::tiny(), frame.area());  // 2x15 characters
    frame.render_widget(RatatuiLogo::small(), frame.area()); // 2x27 characters
}

Results in:

text
▛▚▗▀▖▜▘▞▚▝▛▐ ▌▌
▛▚▐▀▌▐ ▛▜ ▌▝▄▘▌

█▀▀▄ ▄▀▀▄▝▜▛▘▄▀▀▄▝▜▛▘█  █ █
█▀▀▄ █▀▀█ ▐▌ █▀▀█ ▐▌ ▀▄▄▀ █

You can also run the example using:

bash
cargo run --example ratatui-logo

Line: Implement From<Cow<str>> 📜

Line now implements From<Cow<str>> to allow for more flexible conversions.

rust
let cow_str: Cow<'static, str> = Cow::Borrowed("hello, world");
let line = Line::from(cow_str);

As this adds an extra conversion, ambiguous inferred values may no longer compile. In that case, use Line::from(String::from(...)) instead.


Rect::area now returns u32 📏

The Rect::area() function now returns a u32 instead of a u16 to allow for larger areas to be calculated.

Previously, Rect::new() would clamp the rectangle's total area to u16::MAX, maintaining its aspect ratio. Now, it clamps the width and height separately to stay within u16::MAX.


Deprecate block::Title ⚠️

ratatui::widgets::block::Title is deprecated in favor of using Line to represent titles.

This removes an unnecessary layer of wrapping (string -> Span -> Line -> Title).

To update your code:

rust
Block::new().title(Title::from("foo"));
// becomes any of
Block::new().title("foo");
Block::new().title(Line::from("foo"));

Block::new().title(Title::from("foo").position(Position::TOP));
// becomes any of
Block::new().title_top("foo");
Block::new().title_top(Line::from("foo"));

Block::new().title(Title::from("foo").position(Position::BOTTOM));
// becomes any of
Block::new().title_bottom("foo");
Block::new().title_bottom(Line::from("foo"));

The Title struct will be removed in a future release of Ratatui (likely 0.31).

For more information see this issue.


Better Debug output 🐞

The Debug output for Text, Line, Span, and Style has been improved to be more concise and easier to read.

For example, given this code:

rust
Text::styled("Hello, world!", Color::Yellow).centered(),

The Debug output ({:?}) will now look like this:

Text::from(Line::from("Hello, world!")).yellow().centered()


DoubleEndedIterator for Columns and Rows 🔄

You can now iterate over the columns and rows in a layout in reverse order!

rust
let rect = Rect::new(0, 0, 3, 2);
let mut columns = Columns::new(rect);

assert_eq!(columns.next_back(), Some(Rect::new(2, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(1, 0, 1, 2)));
assert_eq!(columns.next_back(), Some(Rect::new(0, 0, 1, 2)));
assert_eq!(columns.next_back(), None);
assert_eq!(columns.next(), None);

:::caution

We also removed the public fields from the Columns,Rows, and Positions iterators since they were not intended to be public and should not have been accessed directly.

:::


Pin unicode-width 📌

We use the unicode-width crate to calculate the width of characters. There was a controversial change in 0.1.14 which resulted in 0.1.13 being published as 0.2.0. This also broke our tests:

rust
assert_eq!("👩".width(), 2); // Woman
assert_eq!("🔬".width(), 2); // Microscope
assert_eq!("👩‍🔬".width(), 4); // Woman scientist -> should be 4 but it expect 2

We decided to comply with these changes by pinning at 0.2.0 to avoid breaking applications when there are breaking changes in the library.

See the discussion in #1271


Check in Cargo.lock ✔️

We added Cargo.lock to the repository due to the benefits it provides:

When kept up to date, this makes it possible to build any git version with the same versions of crates that were used for any version, without it, you can only use the current versions. This makes bugs in semver compatible code difficult to detect.

See:


Other 💼

  • Remove unused dependencies detected with cargo-machete (#1362)
  • Remove the usage of prelude in doc examples (#1390)
  • Add benchmark for Table (#1408)
  • Implement size hints for Rect iterators (#1420)
  • Update README.md (#1431 & #1419)
  • Fix viewport resizing and clearing (#1353 & #1427)

"Food will come, Remy. Food always comes to those who love to cook." – Gusteau