From 36540ea4be58a8ff66d53786736a87125bd33bf5 Mon Sep 17 00:00:00 2001 From: Val Lorentz Date: Sun, 5 Nov 2023 10:53:06 +0100 Subject: [PATCH] Exclude empty columns on the right from the prerender cache --- src/components/backlog.rs | 19 ++++---- src/widgets/bottom_aligned_paragraph.rs | 7 ++- src/widgets/mod.rs | 5 ++- tests/components/backlog.rs | 60 +++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/components/backlog.rs b/src/components/backlog.rs index 83978d3..a8745d6 100644 --- a/src/components/backlog.rs +++ b/src/components/backlog.rs @@ -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, }, ); diff --git a/src/widgets/bottom_aligned_paragraph.rs b/src/widgets/bottom_aligned_paragraph.rs index b090da8..222ac0b 100644 --- a/src/widgets/bottom_aligned_paragraph.rs +++ b/src/widgets/bottom_aligned_paragraph.rs @@ -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) } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 7eb5771..0428cbe 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -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); } /* diff --git a/tests/components/backlog.rs b/tests/components/backlog.rs index 4ced116..a21573a 100644 --- a/tests/components/backlog.rs +++ b/tests/components/backlog.rs @@ -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();