use ratatui_core::{buffer::Buffer, layout::Rect};
use crate::component::Component;
type RenderFn = Box<dyn Fn(Rect, &mut Buffer) + Send + Sync>;
type DesiredHeightFn = Box<dyn Fn(u16) -> u16 + Send + Sync>;
fn noop_render(_: Rect, _: &mut Buffer) {}
#[derive(typed_builder::TypedBuilder)]
pub struct Canvas {
#[builder(default = Box::new(noop_render), setter(transform = |f: impl Fn(Rect, &mut Buffer) + Send + Sync + 'static| Box::new(f) as RenderFn))]
pub render_fn: RenderFn,
#[builder(default, setter(into))]
pub height: Option<u16>,
#[builder(default, setter(transform = |f: impl Fn(u16) -> u16 + Send + Sync + 'static| Some(Box::new(f) as DesiredHeightFn)))]
pub desired_height_fn: Option<DesiredHeightFn>,
}
impl Canvas {
pub fn new(f: impl Fn(Rect, &mut Buffer) + Send + Sync + 'static) -> Self {
Self {
render_fn: Box::new(f),
height: None,
desired_height_fn: None,
}
}
pub fn with_height(mut self, h: u16) -> Self {
self.height = Some(h);
self
}
}
impl Component for Canvas {
type State = ();
fn render(&self, area: Rect, buf: &mut Buffer, _state: &()) {
(self.render_fn)(area, buf);
}
fn desired_height(&self, width: u16, _state: &()) -> Option<u16> {
if let Some(ref f) = self.desired_height_fn {
Some(f(width))
} else {
self.height
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui_core::widgets::Widget;
use ratatui_widgets::paragraph::Paragraph;
#[test]
fn canvas_renders_via_closure() {
let canvas = Canvas::new(|area: Rect, buf: &mut Buffer| {
Paragraph::new("hello").render(area, buf);
});
let area = Rect::new(0, 0, 20, 1);
let mut buf = Buffer::empty(area);
canvas.render(area, &mut buf, &());
assert_eq!(buf.cell((0, 0)).unwrap().symbol(), "h");
assert_eq!(buf.cell((4, 0)).unwrap().symbol(), "o");
}
#[test]
fn canvas_with_height_returns_desired_height() {
let canvas = Canvas::new(|_, _| {}).with_height(5);
assert_eq!(canvas.desired_height(80, &()), Some(5));
}
#[test]
fn canvas_without_height_returns_none() {
let canvas = Canvas::new(|_, _| {});
assert_eq!(canvas.desired_height(80, &()), None);
}
}