use ratatui::{
buffer::Buffer,
layout::Rect,
text::Line,
widgets::{Paragraph, Widget},
};
pub trait Renderable {
fn render(&self, area: Rect, buf: &mut Buffer);
fn desired_height(&self, width: u16) -> u16;
}
#[derive(Default)]
pub struct Column<'a> {
children: Vec<Box<dyn Renderable + 'a>>,
}
impl<'a> Column<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, child: impl Renderable + 'a) {
self.children.push(Box::new(child));
}
pub fn is_empty(&self) -> bool {
self.children.is_empty()
}
}
impl Renderable for Column<'_> {
fn render(&self, area: Rect, buf: &mut Buffer) {
let mut y = area.y;
for child in &self.children {
if y >= area.bottom() {
break;
}
let height = child.desired_height(area.width).min(area.bottom() - y);
if height == 0 {
continue;
}
let child_area = Rect::new(area.x, y, area.width, height);
child.render(child_area, buf);
y = y.saturating_add(height);
}
}
fn desired_height(&self, width: u16) -> u16 {
self.children.iter().fold(0_u16, |height, child| {
height.saturating_add(child.desired_height(width))
})
}
}
#[derive(Clone, Debug, Default)]
pub struct TextCell {
lines: Vec<Line<'static>>,
}
impl TextCell {
pub fn new(lines: Vec<Line<'static>>) -> Self {
Self { lines }
}
}
impl Renderable for TextCell {
fn render(&self, area: Rect, buf: &mut Buffer) {
if !area.is_empty() {
Paragraph::new(self.lines.clone()).render(area, buf);
}
}
fn desired_height(&self, width: u16) -> u16 {
if width == 0 {
return 0;
}
self.lines.iter().fold(0_u16, |height, line| {
let rows = (line.width() as u16).max(1).div_ceil(width);
height.saturating_add(rows)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::{style::Style, text::Span};
#[test]
fn column_measures_and_clips_children_to_its_area() {
let mut column = Column::new();
column.push(TextCell::new(vec![Line::from("one")]));
column.push(TextCell::new(vec![Line::from(Span::styled(
"two",
Style::default(),
))]));
assert_eq!(column.desired_height(10), 2);
let mut buffer = Buffer::empty(Rect::new(0, 0, 10, 1));
column.render(Rect::new(0, 0, 10, 1), &mut buffer);
assert_eq!(buffer[(0, 0)].symbol(), "o");
}
#[test]
fn text_cell_measures_wrapped_lines() {
let cell = TextCell::new(vec![Line::from("123456")]);
assert_eq!(cell.desired_height(4), 2);
}
}