tui-rs
tui-rs copied to clipboard
Lacking extensibility
Problem
I need a way to display a scrolling list of widgets, similar to i.e. card collections in material design. To do this I wrote a simple widget that can draw other widgets but the implementation is a bit lacking since the widgets can't report how large of an area they "need".
Solution
I don't have a solution but I'd like to discuss the option of modifying the API to more easily extend it with custom widgets similar to the card widget.
Additional context
use tui::{
buffer::{Buffer, Cell},
layout::Rect,
widgets::{Block, Widget},
};
/// A Widget that renders a list of widgets. Due to the design of tui-rs it is impossible to know
/// the size of the widget before it is drawn, so the widgets will be drawn to temporary buffers
/// whom are later analyzed and shrunk to fit before being merged into the main buffer. A drawback
/// of this design is that if you add a block with side or bottom borders the entire max size will
/// be used for that widget. So be careful with this one.
pub struct Card<'a, W: Widget> {
items: Vec<W>,
block: Option<Block<'a>>,
max_card_height: u16,
}
impl<'a, W: Widget> Default for Card<'a, W> {
fn default() -> Self {
Self {
items: vec![],
block: None,
max_card_height: 10,
}
}
}
impl<'a, W: Widget> Card<'a, W> {
pub fn new(items: Vec<W>) -> Self {
Self {
items,
..Self::default()
}
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self
}
pub fn max_card_height(mut self, card_height: u16) -> Self {
self.max_card_height = card_height;
self
}
}
impl<'a, W: Widget> tui::widgets::Widget for Card<'a, W> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = self
.block
.map(|block: Block| {
let inner_area = block.inner(area);
block.render(area, buf);
inner_area
})
.unwrap_or(area);
let area = Rect {
height: self.max_card_height,
..area
};
let buffers = self.items.into_iter().scan(area.y, |y, widget| {
let mut buf = Buffer::empty(Rect { y: *y, ..area });
widget.render(*buf.area(), &mut buf);
buf = shrink_buffer(buf);
*y += buf.area().height;
Some(buf)
});
for buffer in buffers {
buf.merge(&buffer);
}
}
}
/// Resizes the buffer by removing untouched rows from the bottom up
fn shrink_buffer(mut buf: Buffer) -> Buffer {
let area = Rect {
height: buf
.content
.iter()
.enumerate()
.fold(0u16, |max_y, (idx, cell)| {
use std::cmp::max;
if *cell != Cell::default() {
max(max_y, (idx as u16) / buf.area().width + 1)
} else {
max_y
}
}),
..*buf.area()
};
buf.resize(area);
buf
}