Exclude empty columns on the right from the prerender cache

This commit is contained in:
2023-11-05 10:53:06 +01:00
parent 3317024231
commit 36540ea4be
4 changed files with 79 additions and 12 deletions

View File

@ -129,7 +129,7 @@ impl Backlog {
// TODO: cache this
let widget = BottomAlignedParagraph::new(item.text).scroll(scroll);
let height = widget.render_overlap(text_area, frame_buffer);
let (_, height) = widget.render_overlap(text_area, frame_buffer);
text_area.height = text_area.height.saturating_sub(height);
scroll = scroll.saturating_sub(expected_height);
@ -150,7 +150,7 @@ impl Backlog {
value: PrerenderValue::Rendered(buf),
}) if *key == text_area.width => {
// We already rendered it, copy the buffer.
assert_eq!(text_area.width, buf.area.width);
assert!(text_area.width >= buf.area.width);
let top_padding = text_area.height.saturating_sub(buf.area.height);
let skip_top_lines = buf.area.height.saturating_sub(text_area.height);
copy_buffer(
@ -158,14 +158,15 @@ impl Backlog {
Rect {
x: 0,
y: 0,
width: text_area.width,
width: buf.area.width,
height: buf.area.height - skip_top_lines,
},
frame_buffer,
Rect {
x: text_area.x,
y: text_area.y + top_padding,
width: buf.area.width,
height: text_area.height - top_padding,
..text_area
},
);
@ -173,7 +174,8 @@ impl Backlog {
},
prerender => {
let widget = BottomAlignedParagraph::new(item.text);
let height = widget.render_overlap(text_area, frame_buffer);
let (drawn_width, height) = widget.render_overlap(text_area, frame_buffer);
assert!(drawn_width <= text_area.width);
// If the whole widget fits in the text_area, copy the drawn result to a buffer
// for caching
@ -181,22 +183,23 @@ impl Backlog {
let mut buf = ratatui::buffer::Buffer::empty(Rect {
x: 0,
y: 0,
width: text_area.width,
width: drawn_width,
height,
});
let top_padding = text_area.height.saturating_sub(buf.area.height);
copy_buffer(
frame_buffer,
Rect {
x: text_area.x,
y: text_area.y + top_padding,
width: drawn_width,
height: text_area.height - top_padding,
..text_area
},
&mut buf,
Rect {
x: 0,
y: 0,
width: text_area.width, // TODO: only copy the width actually drawn by the widget
width: drawn_width,
height: height,
},
);

View File

@ -83,7 +83,7 @@ impl<'a> BottomAlignedParagraph<'a> {
}
impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> u16 {
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16) {
// Inspired by https://github.com/ratatui-org/ratatui/blob/9f371000968044e09545d66068c4ed4ea4b35d8a/src/widgets/paragraph.rs#L214-L275
let lines = self.wrap_lines(area.width);
@ -99,6 +99,8 @@ impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
assert!(lines.len() <= text_area_height);
let mut max_width = 0;
for (y, line) in lines.into_iter().enumerate() {
let mut x = 0;
for StyledGrapheme { symbol, style } in line {
@ -118,8 +120,9 @@ impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
.set_style(*style);
x += width as u16;
}
max_width = u16::max(max_width, x);
}
actual_height as u16
(max_width, actual_height as u16)
}
}

View File

@ -26,9 +26,10 @@ pub use prerender::Prerender;
#[rustfmt::skip] // reflow is vendored from ratatui, let's avoid changes
mod reflow;
/// A [`Widget`] that returns how many lines it actually drew to.
/// A [`Widget`] that returns how many columns and lines it needs to draw everything
/// (which is the number of lines it actually drew if it fits on screen)
pub trait OverlappableWidget {
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> u16;
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16);
}
/*

View File

@ -89,12 +89,72 @@ fn test_single_item_cached() {
text: Text::raw("hello"),
prerender: &prerender,
};
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
bl.draw_items(&mut buf, area, vec![item].into_iter())
.context("Failed to draw")
.unwrap();
assert_eq!(buf, expected);
}
/// Checks that the prerender cache does not store empty columns to the right
#[test]
fn test_only_necessary_width() {
let mut bl = Backlog::default();
let prerender1 = Prerender::new();
let prerender2 = Prerender::new();
let item1 = BufferItem {
text: Text::raw("hi\nworld"),
prerender: &prerender1,
};
let item2 = BufferItem {
text: Text::raw(":)"),
prerender: &prerender2,
};
let mut cell = ratatui::buffer::Cell::default();
cell.set_char('.');
let mut buf = Buffer::filled(rect(0, 0, 18, 7), &cell); // poisoned buffer
let area = rect(3, 1, 12, 5);
bl.draw_items(&mut buf, area, vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
"..................",
"...┌──────────┐...",
"...│hi........│...",
"...│world.....│...",
"...│:)........│...",
"...└──────────┘...",
"..................",
]);
assert_eq!(buf, expected);
assert_eq!(prerender1.key(), Some(10));
let item1 = BufferItem {
text: Text::raw("hi\nworld"),
prerender: &prerender1,
};
let item2 = BufferItem {
text: Text::raw(":)"),
prerender: &prerender2,
};
let mut buf = Buffer::empty(rect(0, 0, 18, 7));
bl.draw_items(&mut buf, area, vec![item2, item1].into_iter())
.context("Failed to draw")
.unwrap();
let expected = Buffer::with_lines(vec![
" ",
" ┌──────────┐ ",
" │hi... │ ", // dots are leftover from the poisoned buffer above
" │world │ ",
" │:) │ ",
" └──────────┘ ",
" ",
]);
assert_eq!(buf, expected);
}
#[test]
fn test_single_item_tight() {
let mut bl = Backlog::default();