ratatui / ratatui

A Rust crate for cooking up terminal user interfaces (TUIs) 👨‍🍳🐀 https://ratatui.rs
https://ratatui.rs
MIT License
10.44k stars 317 forks source link

insert_before dynamic height based on content #1365

Open staehle opened 1 month ago

staehle commented 1 month ago

Problem

Hello! This is just a suggestion -- I have a workaround in my code, but perhaps native support for this could be nice?

There doesn't seem to be native support for line wrapping + insert_before in a viewport. For example:

let line = Line::from(format!("{} => {}", item_name, a_message_that_could_be_short_or_really_long));
terminal.insert_before(1, |buf| {
    Paragraph::new(line)
        .wrap(Wrap { trim: true })
        .render(buf.area, buf);
})?

If, given a terminal wide enough, I would have output like:

some repo name => on 7f9329dc0741a4eeea209497f4e03513454e7606
another repo name => currently on 0d6916b67dd7f53b6242071c89053ebe3347e18e but now checking out 324a1607a49bb37efdcfda90b2505163700eab70

However, on smaller-width terminals, this would instead truncate that line to fit the height argument of 1, which is not what I would expect given my "wrap" call (but is however understandable why it does this):

some repo name => on 7f9329dc0741a4eeea209497f4e03513454e7606
another repo name => currently on

Solution

It would be nice if, for example, there was an additional insert_before_wrap that did not take a height arg and simply calculated it based on the contents, e.g.:

pub fn insert_before_wrap<F>(&mut self, draw_fn: F) -> io::Result<()>
let line = Line::from(format!("{} => {}", item_name, a_message_that_could_be_short_or_really_long));
terminal.insert_before_wrap(|buf| {
    Paragraph::new(line)
        .wrap(Wrap { trim: true })
        .render(buf.area, buf);
})?

Alternatives

My workaround for this is that I needed to manually calculate if the line I'm insert_before'ing would wrap given the current terminal width, and then increase the height arg appropriately if so. e.g.:

let termw = terminal.size().map(|s| s.width).unwrap_or(80);
let line = Line::from(format!("{} => {}", item_name, a_message_that_could_be_short_or_really_long));
let height = 1 + (line.width().try_into().unwrap_or(0) / termw);
terminal.insert_before(height, |buf| {
    Paragraph::new(line)
        .wrap(Wrap { trim: true })
        .render(buf.area, buf);
})?

This would produce this output if the terminal width (80 here) was less than the outputted line:

some repo name => on 7f9329dc0741a4eeea209497f4e03513454e7606
another repo name => currently on 0d6916b67dd7f53b6242071c89053ebe3347e18e but now
checking out 324a1607a49bb37efdcfda90b2505163700eab70

or say 50 width:

some repo name => on
7f9329dc0741a4eeea209497f4e03513454e7606
another repo name => currently on
0d6916b67dd7f53b6242071c89053ebe3347e18e but now
checking out 324a1607a49bb37efdcfda90b2505163700eab70

This is fine, but I need to do this for every insert_before that might have long lines that I still want the user to see.

Thanks!

orhun commented 1 month ago

+1 from me, sounds like it might be a good addition to the viewport.

joshka commented 1 month ago

It would be nice if, for example, there was an additional insert_before_wrap that did not take a height arg and simply calculated it based on the contents, e.g.:

The problem with this is that widgets don't have any inherent height. They fill / fit in the available space. For the Paragraph widget we added a line_count method that can be used to calculate the number of lines that paragraph will use to render given a known width.

Doing this more generically for every widget is a much bigger problem and a fundamental change to how widgets work. Effectively it requires all widgets to have some say in how they are laid out, rather than this being something passed into the widgets. This is a good idea in a general sense, but not one to implement on this method. I'm exploring some ideas in https://github.com/joshka/tui-brulee for using taffy to lay out widgets, but they're in a very early stage.