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

View File

@ -83,7 +83,7 @@ impl<'a> BottomAlignedParagraph<'a> {
} }
impl<'a> OverlappableWidget for 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 // Inspired by https://github.com/ratatui-org/ratatui/blob/9f371000968044e09545d66068c4ed4ea4b35d8a/src/widgets/paragraph.rs#L214-L275
let lines = self.wrap_lines(area.width); let lines = self.wrap_lines(area.width);
@ -99,6 +99,8 @@ impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
assert!(lines.len() <= text_area_height); assert!(lines.len() <= text_area_height);
let mut max_width = 0;
for (y, line) in lines.into_iter().enumerate() { for (y, line) in lines.into_iter().enumerate() {
let mut x = 0; let mut x = 0;
for StyledGrapheme { symbol, style } in line { for StyledGrapheme { symbol, style } in line {
@ -118,8 +120,9 @@ impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
.set_style(*style); .set_style(*style);
x += width as u16; 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 #[rustfmt::skip] // reflow is vendored from ratatui, let's avoid changes
mod reflow; 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 { 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"), text: Text::raw("hello"),
prerender: &prerender, prerender: &prerender,
}; };
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
bl.draw_items(&mut buf, area, vec![item].into_iter()) bl.draw_items(&mut buf, area, vec![item].into_iter())
.context("Failed to draw") .context("Failed to draw")
.unwrap(); .unwrap();
assert_eq!(buf, expected); 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] #[test]
fn test_single_item_tight() { fn test_single_item_tight() {
let mut bl = Backlog::default(); let mut bl = Backlog::default();