How to: Overwrite regions

Tldr

Use the Clear widget to clear areas of the screen to avoid style and symbols from leaking from previously rendered widgets.

Ratatui renders text in the order that the application writes to the buffer. This means that earlier instructions will be overwritten by later ones. However, it’s important to note that widgets do not always clear every cell in the area that they are rendering to. This may cause symbols and styles that were previously rendered to the buffer to “bleed” through into the cells that are rendered on top of those cells.

The following code exhibits this problem:

use lipsum::lipsum;
use ratatui::{prelude::*, widgets::*};

// -- snip --

fn ui(frame: &mut Frame) {
    let area = frame.size();
    let background_text = Paragraph::new(lipsum(1000))
        .wrap(Wrap { trim: true })
        .light_blue()
        .italic()
        .on_black();
    frame.render_widget(background_text, area);

    // take up a third of the screen vertically and half horizontally
    let popup_area = Rect {
        x: area.width / 4,
        y: area.height / 3,
        width: area.width / 2,
        height: area.height / 3,
    };
    let bad_popup = Paragraph::new("Hello world!")
        .wrap(Wrap { trim: true })
        .style(Style::new().yellow())
        .block(
            Block::new()
                .title("Without Clear")
                .title_style(Style::new().white().bold())
                .borders(Borders::ALL)
                .border_style(Style::new().red()),
        );
    frame.render_widget(bad_popup, popup_area);
}

problem

Notice that the background color (black in this case), the italics, and the lorem ipsum background text show through the popup.

This problem is easy to prevent by rendering a Clear widget prior to rendering the main popup. Here is an example of how to use this technique to create a Popup widget:

use derive_setters::Setters;
use lipsum::lipsum;
use ratatui::{prelude::*, widgets::*};

#[derive(Debug, Default, Setters)]
struct Popup<'a> {
    #[setters(into)]
    title: Line<'a>,
    #[setters(into)]
    content: Text<'a>,
    border_style: Style,
    title_style: Style,
    style: Style,
}

impl Widget for Popup<'_> {
    fn render(self, area: Rect, buf: &mut Buffer) {
        // ensure that all cells under the popup are cleared to avoid leaking content
        Clear.render(area, buf);
        let block = Block::new()
            .title(self.title)
            .title_style(self.title_style)
            .borders(Borders::ALL)
            .border_style(self.border_style);
        Paragraph::new(self.content)
            .wrap(Wrap { trim: true })
            .style(self.style)
            .block(block)
            .render(area, buf);
    }
}

We can use the new Popup widget with the following code:

    let popup = Popup::default()
        .content("Hello world!")
        .style(Style::new().yellow())
        .title("With Clear")
        .title_style(Style::new().white().bold())
        .border_style(Style::new().red());
    frame.render_widget(popup, popup_area);

Which results in the following:

demo

Notice that the background is set to the default background and there are no italics or symbols from the background text.

Full source for this article is available at https://github.com/ratatui-org/ratatui-book/tree/main/code/how-to-overwrite-regions