use retroglyph_core::Rect;
use retroglyph_core::text::Line;
use super::{PrintLine, Widget};
use crate::Surface;
#[derive(Clone, Copy, Debug)]
pub struct Log<'a> {
messages: &'a [Line],
offset: usize,
}
impl<'a> Log<'a> {
#[must_use]
pub const fn new(messages: &'a [Line]) -> Self {
Self {
messages,
offset: 0,
}
}
#[must_use]
pub const fn offset(mut self, offset: usize) -> Self {
self.offset = offset;
self
}
}
impl Widget for Log<'_> {
fn render(&self, area: Rect, surface: &mut Surface<'_>) {
let visible_height = area.height_usize();
if area.width() == 0 || visible_height == 0 {
return;
}
let Some(bottom) = self
.messages
.len()
.checked_sub(self.offset.saturating_add(1))
else {
return;
};
let top = bottom.saturating_sub(visible_height - 1);
for (row, message) in self.messages[top..=bottom].iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
let y = area.top() + row as u16;
let row_area = Rect::new(area.left(), y, area.width(), 1);
PrintLine::new(message).render(row_area, surface);
}
}
}
#[cfg(test)]
mod tests {
use retroglyph_core::{Grid, Pos};
use super::*;
fn lines(texts: &[&str]) -> Vec<Line> {
texts.iter().map(|t| Line::raw(*t)).collect()
}
#[test]
fn shows_the_most_recent_messages_oldest_at_top() {
let area = Rect::new(0, 0, 20, 2);
let messages = lines(&["alpha", "bravo", "charlie", "delta"]);
let mut grid = Grid::new(20, 2);
Log::new(&messages).render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'c'); assert_eq!(grid[Pos::new(0, 1)].glyph(), 'd'); }
#[test]
fn offset_scrolls_back_through_history() {
let area = Rect::new(0, 0, 20, 2);
let messages = lines(&["alpha", "bravo", "charlie", "delta"]);
let mut grid = Grid::new(20, 2);
Log::new(&messages)
.offset(1)
.render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'b'); assert_eq!(grid[Pos::new(0, 1)].glyph(), 'c'); }
#[test]
fn offset_past_the_start_shows_fewer_lines_without_panicking() {
let area = Rect::new(0, 0, 20, 2);
let messages = lines(&["alpha", "bravo"]);
let mut grid = Grid::new(20, 2);
Log::new(&messages)
.offset(5)
.render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
}
#[test]
fn fewer_messages_than_visible_rows_leaves_the_rest_untouched() {
let area = Rect::new(0, 0, 20, 4);
let messages = lines(&["only"]);
let mut grid = Grid::new(20, 4);
Log::new(&messages).render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'o'); assert_eq!(grid[Pos::new(0, 1)].glyph(), ' '); assert_eq!(grid[Pos::new(0, 2)].glyph(), ' '); }
#[test]
fn clips_long_lines_to_area_width() {
let area = Rect::new(0, 0, 5, 1);
let messages = lines(&["a much longer message than fits"]);
let mut grid = Grid::new(5, 1);
Log::new(&messages).render(area, &mut Surface::new(&mut grid, area, 0));
assert_eq!(grid[Pos::new(4, 0)].glyph(), 'c');
}
}