The Basic Building blocks of Ratatui - Part 4

Author

Dheepak Krishnamurthy

Published

May 18, 2024

Keywords

rust, ratatui

Ratatui is a crate for building terminal user interfaces in Rust.

In this post we’ll show some simple widgets that come built-in with ratatui.

Code
:dep ratatui = "0.26.2"
:dep ratatui-macros = "0.4.0"
    
fn span_to_html(s: ratatui::text::Span) -> String{
    let mut html = String::new();
    html.push_str("<span style=\"");

    // Set foreground color
    if let Some(color) = &s.style.fg {
        html.push_str(&format!("color: {};", color));
    }

    // Set background color
    if let Some(color) = &s.style.bg {
        html.push_str(&format!("background-color: {};", color));
    }

    // Add modifiers
    match s.style.add_modifier {
        ratatui::style::Modifier::BOLD => html.push_str("font-weight: bold;"),
        ratatui::style::Modifier::ITALIC => html.push_str("font-style: italic;"),
        ratatui::style::Modifier::UNDERLINED => html.push_str("text-decoration: underline;"),
        _ => {}
    }
    html.push_str("\">");
    html.push_str(&s.content);
    html.push_str("</span>");
    html
}

fn buffer_to_html(buf: &ratatui::buffer::Buffer) -> String {
    fn escape_special_html_characters(text: &str) -> String {
        text.replace("&", "&amp;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")
            .replace("\"", "&quot;")
            .replace("'", "&#39;")
    }

    let mut html = String::from("<pre><code>");

    let w = buf.area.width;
    let h = buf.area.height;

    for y in 0..h {
        for x in 0..w {
            let s = buf.get(x, y).symbol();
            
            let escaped = escape_special_html_characters(s); 

            let style = buf.get(x, y).style();

            let span = ratatui::text::Span::styled(s, style);
            
            html.push_str(&span_to_html(span));
        }
        html.push('\n');
    }

    html.push_str("</code></pre>");

    html 
}
    
fn show_html<D>(content: D) where D: std::fmt::Display {
    println!(r#"EVCXR_BEGIN_CONTENT text/html
<div style="display: flex; justify-content:start; gap: 1em; margin: 1em">
{}
</div>
EVCXR_END_CONTENT"#, content);
}

Widget primitives

Block

The simplest widget is the Block widget, which is essentially just borders.

use ratatui::widgets::Widget;

let (x, y, width, height) = (0, 0, 50, 5); 
let area = ratatui::layout::Rect::new(x, y, width, height);
let mut buf = ratatui::buffer::Buffer::empty(area);
ratatui::widgets::Block::bordered().render(area, &mut buf);

show_html(buffer_to_html(&buf))

                                                
                                                
                                                

Most widgets accept a Block as a fluent setter. We saw from earlier that the Paragraph has a .block() method that accepts a Block.

let paragraph = Paragraph::new(text).block(block).centered();

Blocks can have different kinds of borders:

let (x, y, width, height) = (0, 0, 50, 5); 
let area = ratatui::layout::Rect::new(x, y, width, height);
let mut buf = ratatui::buffer::Buffer::empty(area);

ratatui::widgets::Block::bordered().border_type(
    ratatui::widgets::BorderType::Double
).render(area.inner(&ratatui::layout::Margin::new(2, 1)), &mut buf);

ratatui::widgets::Block::bordered().borders(
    ratatui::widgets::Borders::TOP | ratatui::widgets::Borders::BOTTOM
).render(area, &mut buf);

show_html(buffer_to_html(&buf))

    
                                                
    

And Block can have multiple titles in different locations:

use ratatui_macros::line;

let (x, y, width, height) = (0, 0, 50, 5); 
let area = ratatui::layout::Rect::new(x, y, width, height);
let mut buf = ratatui::buffer::Buffer::empty(area);

let block = ratatui::widgets::Block::bordered()
                .title("Top Left") // accepts anything that can be converted to a `Title` or a `Line`
                .title(ratatui::text::Line::from("Top Center").centered()) // explicitly need to use `Line` if you want alignment
                .title(line!["Top Right"].right_aligned()) // you can use the `line!` macro to make it shorter
                .title(ratatui::widgets::block::Title::from("Bottom Right") // explicitly using `Title` gives you most control
                       .alignment(ratatui::layout::Alignment::Right)
                       .position(ratatui::widgets::block::title::Position::Bottom)
                )
                .title_bottom(ratatui::text::Line::from("Bottom Center").centered()) // shorthand functions for bottom position
                .title_bottom("Bottom Left"); // aligned to the left by default

block.render(area, &mut buf);

show_html(buffer_to_html(&buf))
Top LeftTop CenterTop Right
                                                
                                                
                                                
Bottom LeftBottom CenterBottom Right

Conclusion

In the next post, we’ll examine how Ratatui works under the hood in more detail.

Reuse

Citation

BibTeX citation:
@online{krishnamurthy2024,
  author = {Krishnamurthy, Dheepak},
  title = {The {Basic} {Building} Blocks of {Ratatui} - {Part} 4},
  date = {2024-05-18},
  url = {https://kdheepak.com/blog/the-basic-building-blocks-of-ratatui-part-4},
  langid = {en}
}
For attribution, please cite this work as:
D. Krishnamurthy, “The Basic Building blocks of Ratatui - Part 4,” May 18, 2024. https://kdheepak.com/blog/the-basic-building-blocks-of-ratatui-part-4.