Preserve current position when new items are added to a buffer
This commit is contained in:
@ -32,7 +32,7 @@ use crate::widgets::Prerender;
|
||||
const MAX_MEM_LOG_LINES: usize = 1000;
|
||||
|
||||
pub struct LogBuffer {
|
||||
lines: VecDeque<(String, Prerender)>,
|
||||
lines: VecDeque<(u64, String, Prerender)>,
|
||||
receiver: UnboundedReceiver<String>,
|
||||
}
|
||||
|
||||
@ -73,17 +73,29 @@ impl Buffer for LogBuffer {
|
||||
if self.lines.len() >= MAX_MEM_LOG_LINES {
|
||||
self.lines.pop_front();
|
||||
}
|
||||
self.lines.push_back((line, Prerender::new()));
|
||||
let line_id = self
|
||||
.lines
|
||||
.back()
|
||||
.map(|(last_id, _, _)| last_id + 1)
|
||||
.unwrap_or(0);
|
||||
self.lines.push_back((line_id, line, Prerender::new()));
|
||||
}
|
||||
|
||||
fn content<'a>(&'a self) -> Box<dyn ExactSizeIterator<Item = BufferItem<'a>> + 'a> {
|
||||
use ansi_to_tui::IntoText;
|
||||
Box::new(self.lines.iter().rev().map(|(line, prerender)| BufferItem {
|
||||
content: BufferItemContent::Text(line.clone().into_text().unwrap_or_else(|e| {
|
||||
tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e);
|
||||
Text::raw(line)
|
||||
})),
|
||||
prerender,
|
||||
}))
|
||||
Box::new(
|
||||
self
|
||||
.lines
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(line_id, line, prerender)| BufferItem {
|
||||
content: BufferItemContent::Text(line.clone().into_text().unwrap_or_else(|e| {
|
||||
tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e);
|
||||
Text::raw(line)
|
||||
})),
|
||||
prerender,
|
||||
unique_id: Some(*line_id),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -87,9 +87,15 @@ pub enum BufferItemContent<'buf> {
|
||||
Empty,
|
||||
}
|
||||
|
||||
// intentionally not Clone, because it would be ambiguous what to do
|
||||
// with the prerender if the content is updated only in one instance
|
||||
pub struct BufferItem<'buf> {
|
||||
pub content: BufferItemContent<'buf>,
|
||||
pub prerender: &'buf Prerender,
|
||||
/// Used to preserve position when other items are added or edited in recent history.
|
||||
///
|
||||
/// Only needs to be unique per-buffer
|
||||
pub unique_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -90,7 +90,7 @@ pub struct SingleClientRoomBuffer {
|
||||
room_id: OwnedRoomId,
|
||||
client: Client,
|
||||
|
||||
items: imbl::vector::Vector<(OwnedBufferItemContent, Prerender)>,
|
||||
items: imbl::vector::Vector<(Option<u64>, OwnedBufferItemContent, Prerender)>,
|
||||
// TODO: get rid of this trait object, we know it's matrix_sdk_ui::timeline::TimelineStream
|
||||
timeline_stream: Box<dyn Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>> + Send + Sync + Unpin>,
|
||||
timeline: Arc<Timeline>,
|
||||
@ -105,7 +105,9 @@ impl DynamicUsage for SingleClientRoomBuffer {
|
||||
+ self
|
||||
.items
|
||||
.iter()
|
||||
.map(|(content, prerender)| content.dynamic_usage() + prerender.dynamic_usage())
|
||||
.map(|(_, content, prerender)| {
|
||||
std::mem::size_of::<Option<u64>>() + content.dynamic_usage() + prerender.dynamic_usage()
|
||||
})
|
||||
.sum::<usize>()
|
||||
}
|
||||
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
|
||||
@ -117,7 +119,11 @@ impl DynamicUsage for SingleClientRoomBuffer {
|
||||
.items
|
||||
.iter()
|
||||
.map(|item| item.0.dynamic_usage_bounds())
|
||||
.chain(self.items.iter().map(|item| item.1.dynamic_usage_bounds())),
|
||||
.chain(self.items.iter().map(|item| item.1.dynamic_usage_bounds()))
|
||||
.chain([(
|
||||
std::mem::size_of::<Option<u64>>() * self.items.len(),
|
||||
Some(std::mem::size_of::<Option<u64>>() * self.items.len()),
|
||||
)]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -139,7 +145,7 @@ impl SingleClientRoomBuffer {
|
||||
let Some(changes) = changes else { return None; };
|
||||
for change in changes {
|
||||
change
|
||||
.map(|item| (self.format_timeline_item(item), Prerender::new()))
|
||||
.map(|item| (Some(item.unique_id()), self.format_timeline_item(item), Prerender::new()))
|
||||
.apply(&mut self.items);
|
||||
}
|
||||
None
|
||||
@ -400,7 +406,7 @@ impl RoomBuffer {
|
||||
timeline: Arc::new(timeline),
|
||||
items: items // FIXME: it's always empty. why?
|
||||
.into_iter()
|
||||
.map(|item| (text!("Initial item: {:#?}", item), Prerender::new()))
|
||||
.map(|item| (None, text!("Initial item: {:#?}", item), Prerender::new()))
|
||||
.collect(),
|
||||
timeline_stream: Box::new(timeline_stream),
|
||||
back_pagination_request: AtomicU16::new(0),
|
||||
@ -534,13 +540,14 @@ impl Buffer for RoomBuffer {
|
||||
.items
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|(line, prerender)| BufferItem {
|
||||
.map(|(line_id, line, prerender)| BufferItem {
|
||||
content: match line {
|
||||
OwnedBufferItemContent::Text(text) => BufferItemContent::Text(Text::raw(text)),
|
||||
OwnedBufferItemContent::Divider(text) => BufferItemContent::Divider(Text::raw(text)),
|
||||
OwnedBufferItemContent::Empty => BufferItemContent::Empty,
|
||||
},
|
||||
prerender,
|
||||
unique_id: *line_id,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
@ -30,9 +30,24 @@ use crate::widgets::{
|
||||
use super::Component;
|
||||
use crate::buffers::{BufferItem, BufferItemContent};
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Debug)]
|
||||
struct ScrollPosition {
|
||||
/// unique_id of the buffer item the scroll is relative to
|
||||
anchor: u64,
|
||||
/// number of lines from the top of the item where the bottom of the viewport is
|
||||
relative_scroll: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Backlog {
|
||||
scroll: u64,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
/// Fallback used if the scroll_position is missing or unusable
|
||||
absolute_scroll: u64,
|
||||
/// How many lines up (or down) the user requested the viewport to move since the last
|
||||
/// render.
|
||||
///
|
||||
/// This is applied to `scroll_position` and `fallback_scroll` on the next render.
|
||||
pending_scroll: i64,
|
||||
}
|
||||
|
||||
impl Component for Backlog {
|
||||
@ -67,8 +82,8 @@ impl Component for Backlog {
|
||||
buffers: &crate::buffers::Buffers,
|
||||
) -> Result<()> {
|
||||
let active_buffer = buffers.active_buffer();
|
||||
let items = active_buffer.content();
|
||||
let undrawn_widgets_at_top = self.draw_items(frame.buffer_mut(), area, items)?;
|
||||
let undrawn_widgets_at_top =
|
||||
self.draw_items(frame.buffer_mut(), area, || active_buffer.content())?;
|
||||
|
||||
// We are reaching the end of the backlog we have locally, ask the buffer to fetch
|
||||
// more if it can
|
||||
@ -82,10 +97,10 @@ impl Component for Backlog {
|
||||
|
||||
impl Backlog {
|
||||
pub fn scroll_up(&mut self, lines: u64) {
|
||||
self.scroll = self.scroll.saturating_add(lines);
|
||||
self.pending_scroll = self.pending_scroll.saturating_add(lines as i64);
|
||||
}
|
||||
pub fn scroll_down(&mut self, lines: u64) {
|
||||
self.scroll = self.scroll.saturating_sub(20);
|
||||
self.pending_scroll = self.pending_scroll.saturating_sub(lines as i64);
|
||||
}
|
||||
|
||||
fn build_widget<'a>(&self, content: BufferItemContent<'a>, scroll: u64) -> BacklogItemWidget<'a> {
|
||||
@ -102,50 +117,94 @@ impl Backlog {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_item_height(&self, item: &BufferItem<'_>, width: u16) -> u64 {
|
||||
match item.prerender.0.lock().unwrap().deref_mut() {
|
||||
Some(PrerenderInner {
|
||||
key,
|
||||
value: PrerenderValue::Rendered(buf),
|
||||
}) if *key == width => buf.area().height as u64,
|
||||
Some(PrerenderInner {
|
||||
key,
|
||||
value: PrerenderValue::NotRendered(height),
|
||||
}) if *key == width => *height,
|
||||
prerender => {
|
||||
let widget = self.build_widget(item.content.clone(), 0);
|
||||
// widget.height() needs to run the whole word-wrapping, which is almost as
|
||||
// expensive as the real render.
|
||||
// This is particularly wasteful, as the last widget.height() call here will
|
||||
// duplicate the work we do in widget.render_overlap() later in the loop.
|
||||
// Unfortunately I can't find a way to make it work because of the lifetimes
|
||||
// involved.
|
||||
let expected_height = widget.height(width);
|
||||
*prerender = Some(PrerenderInner {
|
||||
key: width,
|
||||
value: PrerenderValue::NotRendered(expected_height),
|
||||
});
|
||||
expected_height
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how many items were not drawn because they are too high up the backlog
|
||||
/// (ie. older than the currently displayed items)
|
||||
pub fn draw_items<'a>(
|
||||
pub fn draw_items<'a, Items: ExactSizeIterator<Item = BufferItem<'a>>>(
|
||||
&mut self,
|
||||
frame_buffer: &mut Buffer,
|
||||
area: Rect,
|
||||
mut items: impl ExactSizeIterator<Item = BufferItem<'a>>,
|
||||
get_items: impl Fn() -> Items,
|
||||
) -> Result<usize> {
|
||||
let block = Block::new().borders(Borders::ALL);
|
||||
let mut text_area = block.inner(area);
|
||||
block.render(area, frame_buffer);
|
||||
|
||||
let mut scroll = self.scroll;
|
||||
// Recompute absolute scroll position if we are not at the bottom of the backlog
|
||||
if self.absolute_scroll != 0 || self.pending_scroll != 0 {
|
||||
if let Some(scroll_position) = self.scroll_position.as_ref() {
|
||||
if !get_items().any(|item| item.unique_id == Some(scroll_position.anchor)) {
|
||||
// The anchor doesn't exist anymore, invalidate it
|
||||
self.scroll_position = None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(scroll_position) = self.scroll_position.as_ref() {
|
||||
// Compute the current absolute scroll from the anchor.
|
||||
let mut found_anchor = false;
|
||||
self.absolute_scroll = 0;
|
||||
for item in get_items() {
|
||||
self.absolute_scroll = self
|
||||
.absolute_scroll
|
||||
.saturating_add(self.get_item_height(&item, text_area.width));
|
||||
if item.unique_id == Some(scroll_position.anchor) {
|
||||
found_anchor = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert!(found_anchor, "anchor disappeared between get_items() calls");
|
||||
self.absolute_scroll = self
|
||||
.absolute_scroll
|
||||
.saturating_add_signed(-scroll_position.relative_scroll);
|
||||
}
|
||||
|
||||
if self.pending_scroll != 0 {
|
||||
self.absolute_scroll = self
|
||||
.absolute_scroll
|
||||
.saturating_add_signed(self.pending_scroll);
|
||||
self.scroll_position = None;
|
||||
self.pending_scroll = 0;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(self.pending_scroll, 0, "pending_scroll was not applied");
|
||||
|
||||
let mut items = get_items();
|
||||
let mut scroll = self.absolute_scroll;
|
||||
|
||||
// Skip widgets at the bottom (if scrolled up), and render the first visible one
|
||||
loop {
|
||||
let Some(item) = items.next() else {
|
||||
break;
|
||||
};
|
||||
let expected_height = match item.prerender.0.lock().unwrap().deref_mut() {
|
||||
Some(PrerenderInner {
|
||||
key,
|
||||
value: PrerenderValue::Rendered(buf),
|
||||
}) if *key == text_area.width => buf.area().height as u64,
|
||||
Some(PrerenderInner {
|
||||
key,
|
||||
value: PrerenderValue::NotRendered(height),
|
||||
}) if *key == text_area.width => *height,
|
||||
prerender => {
|
||||
let widget = self.build_widget(item.content.clone(), 0);
|
||||
// widget.height() needs to run the whole word-wrapping, which is almost as
|
||||
// expensive as the real render.
|
||||
// This is particularly wasteful, as the last widget.height() call here will
|
||||
// duplicate the work we do in widget.render_overlap() later in the loop.
|
||||
// Unfortunately I can't find a way to make it work because of the lifetimes
|
||||
// involved.
|
||||
let expected_height = widget.height(text_area.width);
|
||||
*prerender = Some(PrerenderInner {
|
||||
key: text_area.width,
|
||||
value: PrerenderValue::NotRendered(expected_height),
|
||||
});
|
||||
expected_height
|
||||
},
|
||||
};
|
||||
let expected_height = self.get_item_height(&item, text_area.width);
|
||||
|
||||
if scroll.saturating_sub(expected_height) > text_area.height.into() {
|
||||
// Paragraph is too far down, not displayed
|
||||
@ -158,6 +217,18 @@ impl Backlog {
|
||||
let (_, height) = widget.render_overlap(text_area, frame_buffer);
|
||||
text_area.height = text_area.height.saturating_sub(height);
|
||||
|
||||
if self.absolute_scroll != 0 && expected_height >= scroll {
|
||||
// If we are not at the bottom of the backlog and this is the first item up enough
|
||||
// to be visible, set it as anchor for next render
|
||||
if let Some(anchor) = item.unique_id {
|
||||
self.scroll_position = Some(ScrollPosition {
|
||||
anchor,
|
||||
relative_scroll: (expected_height - scroll) as i64, // legal because always positive
|
||||
});
|
||||
}
|
||||
// TODO: if item.unique_id is None, pick the next item that has an id
|
||||
}
|
||||
|
||||
scroll = scroll.saturating_sub(expected_height);
|
||||
if scroll == 0 {
|
||||
break;
|
||||
|
@ -31,6 +31,23 @@ fn rect(x: u16, y: u16, width: u16, height: u16) -> Rect {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! items_iter {
|
||||
[ $($item: expr),* ] => {
|
||||
|| vec![
|
||||
$(
|
||||
{
|
||||
let BufferItem { content, prerender, unique_id } = &$item;
|
||||
BufferItem {
|
||||
content: content.clone(),
|
||||
prerender,
|
||||
unique_id: *unique_id,
|
||||
}
|
||||
},
|
||||
)*
|
||||
].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_item() {
|
||||
let mut bl = Backlog::default();
|
||||
@ -38,9 +55,10 @@ fn test_single_item() {
|
||||
let item = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hello")),
|
||||
prerender: &prerender,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
|
||||
bl.draw_items(&mut buf, rect(3, 2, 12, 4), vec![item].into_iter())
|
||||
bl.draw_items(&mut buf, rect(3, 2, 12, 4), items_iter![item])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -64,10 +82,11 @@ fn test_single_item_cached() {
|
||||
let item = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hello")),
|
||||
prerender: &prerender,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 18, 8));
|
||||
let area = rect(3, 2, 12, 4);
|
||||
bl.draw_items(&mut buf, area, vec![item].into_iter())
|
||||
bl.draw_items(&mut buf, area, items_iter![item])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -88,9 +107,10 @@ fn test_single_item_cached() {
|
||||
let item = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hello")),
|
||||
prerender: &prerender,
|
||||
unique_id: None,
|
||||
};
|
||||
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, items_iter![item])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
assert_eq!(buf, expected);
|
||||
@ -105,16 +125,18 @@ fn test_only_necessary_width() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi\nworld")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw(":)")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
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())
|
||||
bl.draw_items(&mut buf, area, items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -134,13 +156,15 @@ fn test_only_necessary_width() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi\nworld")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw(":)")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 18, 7));
|
||||
bl.draw_items(&mut buf, area, vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, area, items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
@ -162,9 +186,10 @@ fn test_single_item_tight() {
|
||||
let item = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hello")),
|
||||
prerender: &prerender,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 13, 7));
|
||||
bl.draw_items(&mut buf, rect(3, 2, 7, 3), vec![item].into_iter())
|
||||
bl.draw_items(&mut buf, rect(3, 2, 7, 3), items_iter![item])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -187,14 +212,16 @@ fn test_two_items() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let prerender2 = Prerender::new();
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -219,13 +246,15 @@ fn test_two_items_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: Some(456),
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -245,13 +274,15 @@ fn test_two_items_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: Some(456),
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -271,13 +302,15 @@ fn test_two_items_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: Some(456),
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -300,14 +333,16 @@ fn test_two_items_multiline() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let prerender2 = Prerender::new();
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world\n!")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -330,14 +365,16 @@ fn test_two_items_tight() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let prerender2 = Prerender::new();
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 9, 6));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 7, 4), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 7, 4), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
|
||||
@ -359,11 +396,12 @@ fn test_cache_moved() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
|
||||
// Draw once
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
@ -381,14 +419,16 @@ fn test_cache_moved() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let prerender2 = Prerender::new();
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("world")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), vec![item2, item1].into_iter())
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
@ -413,20 +453,23 @@ fn test_overflow_and_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line1 x")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line2 y\nline3 y\nline4 y")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let item3 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line5 z")),
|
||||
prerender: &prerender3,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(
|
||||
&mut buf,
|
||||
rect(1, 1, 12, 5),
|
||||
vec![item3, item2, item1].into_iter(),
|
||||
items_iter![item3, item2, item1],
|
||||
)
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
@ -446,20 +489,23 @@ fn test_overflow_and_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line1 x")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line2 y\nline3 y\nline4 y")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let item3 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line5 z")),
|
||||
prerender: &prerender3,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(
|
||||
&mut buf,
|
||||
rect(1, 1, 12, 5),
|
||||
vec![item3, item2, item1].into_iter(),
|
||||
items_iter![item3, item2, item1],
|
||||
)
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
@ -479,20 +525,23 @@ fn test_overflow_and_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line1 x")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line2 y\nline3 y\nline4 y")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let item3 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line5 z")),
|
||||
prerender: &prerender3,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(
|
||||
&mut buf,
|
||||
rect(1, 1, 12, 5),
|
||||
vec![item3, item2, item1].into_iter(),
|
||||
items_iter![item3, item2, item1],
|
||||
)
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
@ -511,20 +560,23 @@ fn test_overflow_and_scroll() {
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line1 x")),
|
||||
prerender: &prerender1,
|
||||
unique_id: None,
|
||||
};
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line2 y\nline3 y\nline4 y")),
|
||||
prerender: &prerender2,
|
||||
unique_id: None,
|
||||
};
|
||||
let item3 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("line5 z")),
|
||||
prerender: &prerender3,
|
||||
unique_id: None,
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(
|
||||
&mut buf,
|
||||
rect(1, 1, 12, 5),
|
||||
vec![item3, item2, item1].into_iter(),
|
||||
items_iter![item3, item2, item1],
|
||||
)
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
@ -539,3 +591,79 @@ fn test_overflow_and_scroll() {
|
||||
]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scrolledup_new_line() {
|
||||
let mut bl = Backlog::default();
|
||||
let prerender1 = Prerender::new();
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi\nworld")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
|
||||
// Draw once
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ┌──────────┐ ",
|
||||
" │ │ ",
|
||||
" │hi │ ",
|
||||
" │world │ ",
|
||||
" └──────────┘ ",
|
||||
" ",
|
||||
]);
|
||||
assert_eq!(buf, expected);
|
||||
|
||||
// Scroll up one line
|
||||
bl.scroll_up(1);
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi\nworld")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ┌──────────┐ ",
|
||||
" │ │ ",
|
||||
" │ │ ",
|
||||
" │hi │ ",
|
||||
" └──────────┘ ",
|
||||
" ",
|
||||
]);
|
||||
assert_eq!(buf, expected);
|
||||
|
||||
// New item added at bottom, displayed paragraph should not move up
|
||||
let item1 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("hi\nworld")),
|
||||
prerender: &prerender1,
|
||||
unique_id: Some(123),
|
||||
};
|
||||
let prerender2 = Prerender::new();
|
||||
let item2 = BufferItem {
|
||||
content: BufferItemContent::Text(Text::raw("!")),
|
||||
prerender: &prerender2,
|
||||
unique_id: Some(456),
|
||||
};
|
||||
let mut buf = Buffer::empty(rect(0, 0, 14, 7));
|
||||
bl.draw_items(&mut buf, rect(1, 1, 12, 5), items_iter![item2, item1])
|
||||
.context("Failed to draw")
|
||||
.unwrap();
|
||||
let expected = Buffer::with_lines(vec![
|
||||
" ",
|
||||
" ┌──────────┐ ",
|
||||
" │ │ ",
|
||||
" │ │ ",
|
||||
" │hi │ ",
|
||||
" └──────────┘ ",
|
||||
" ",
|
||||
]);
|
||||
assert_eq!(buf, expected);
|
||||
}
|
||||
|
Reference in New Issue
Block a user