use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
pub trait Renderable {
fn render(&self, area: Rect, buf: &mut Buffer);
fn desired_height(&self, width: u16) -> u16;
fn render_skip(&self, area: Rect, skip: u16, buf: &mut Buffer) {
let _ = skip;
self.render(area, buf);
}
}
pub struct FlexRenderable {
children: Vec<FlexChild>,
}
struct FlexChild {
flex: i32,
child: Box<dyn Renderable>,
}
impl FlexRenderable {
pub const fn new() -> Self {
Self { children: vec![] }
}
pub fn push(&mut self, flex: i32, child: impl Renderable + 'static) {
self.children.push(FlexChild {
flex,
child: Box::new(child),
});
}
fn allocate(&self, area: Rect) -> Vec<Rect> {
let n = self.children.len();
if n == 0 {
return vec![];
}
let mut sizes = vec![0u16; n];
let mut allocated = 0u16;
let mut total_flex: i32 = 0;
let mut last_flex_idx: usize = 0;
let max_h = area.height;
for (i, fc) in self.children.iter().enumerate() {
if fc.flex > 0 {
total_flex += fc.flex;
last_flex_idx = i;
} else {
let h = fc
.child
.desired_height(area.width)
.min(max_h.saturating_sub(allocated));
sizes[i] = h;
allocated = allocated.saturating_add(h);
}
}
let free_space = max_h.saturating_sub(allocated);
if total_flex > 0 && free_space > 0 {
let total_flex = u16::try_from(total_flex).unwrap_or(u16::MAX);
let space_per_flex = free_space / total_flex;
let mut allocated_flex = 0u16;
for (i, fc) in self.children.iter().enumerate() {
if fc.flex > 0 {
let max_child = if i == last_flex_idx {
free_space.saturating_sub(allocated_flex)
} else {
space_per_flex.saturating_mul(u16::try_from(fc.flex).unwrap_or(u16::MAX))
};
let h = fc.child.desired_height(area.width).min(max_child);
sizes[i] = h;
allocated_flex = allocated_flex.saturating_add(h);
}
}
}
let mut rects = Vec::with_capacity(n);
let mut y = area.y;
for &h in &sizes {
rects.push(Rect::new(area.x, y, area.width, h));
y = y.saturating_add(h);
}
rects
}
}
impl Renderable for FlexRenderable {
fn render(&self, area: Rect, buf: &mut Buffer) {
let rects = self.allocate(area);
for (rect, fc) in rects.into_iter().zip(self.children.iter()) {
if !rect.is_empty() {
fc.child.render(rect, buf);
}
}
}
fn desired_height(&self, width: u16) -> u16 {
let rects = self.allocate(Rect::new(0, 0, width, u16::MAX));
rects.last().map_or(0, |r| r.bottom())
}
}
pub struct ViewportCulledColumn {
children: Vec<Box<dyn Renderable>>,
scroll: u16,
}
impl ViewportCulledColumn {
pub fn new() -> Self {
Self {
children: vec![],
scroll: 0,
}
}
pub fn push(&mut self, child: impl Renderable + 'static) {
self.children.push(Box::new(child));
}
pub const fn set_scroll(&mut self, scroll: u16) {
self.scroll = scroll;
}
}
impl Renderable for ViewportCulledColumn {
fn render(&self, area: Rect, buf: &mut Buffer) {
let viewport_top = self.scroll;
let viewport_bottom = self.scroll.saturating_add(area.height);
let mut y: u16 = 0;
for child in &self.children {
let child_h = child.desired_height(area.width);
let child_bottom = y.saturating_add(child_h);
if child_bottom > viewport_top && y < viewport_bottom {
let skip = viewport_top.saturating_sub(y);
let screen_y = area.y.saturating_add(y.saturating_sub(viewport_top));
let visible_h = (child_h - skip)
.min(area.height.saturating_sub(screen_y.saturating_sub(area.y)));
if visible_h == 0 {
y = child_bottom;
continue;
}
let child_area = Rect::new(area.x, screen_y, area.width, visible_h);
child.render_skip(child_area, skip, buf);
}
y = y.saturating_add(child_h);
if y >= viewport_bottom {
break;
}
}
}
fn desired_height(&self, width: u16) -> u16 {
self.children.iter().map(|c| c.desired_height(width)).sum()
}
}