use ratatui::{
buffer::Buffer,
layout::{Position, Rect},
widgets::{StatefulWidget, Widget},
};
#[derive(Clone, Copy)]
struct InputViewport {
delta_x: i32,
delta_y: i32,
clip: Rect,
}
fn translated(rect: Rect, x: i32, y: i32) -> Rect {
let left = (i32::from(rect.x) + x).clamp(0, i32::from(u16::MAX));
let top = (i32::from(rect.y) + y).clamp(0, i32::from(u16::MAX));
let right = (i32::from(rect.right()) + x).clamp(0, i32::from(u16::MAX));
let bottom = (i32::from(rect.bottom()) + y).clamp(0, i32::from(u16::MAX));
Rect::new(
left as u16,
top as u16,
right.saturating_sub(left) as u16,
bottom.saturating_sub(top) as u16,
)
}
pub struct ComponentDrawer<'a, 'b: 'a> {
pub area: ratatui::layout::Rect,
pub frame: &'a mut ratatui::Frame<'b>,
scroll_buffers: Vec<Buffer>,
input_viewports: Vec<InputViewport>,
}
impl<'a, 'b> ComponentDrawer<'a, 'b> {
pub fn new(frame: &'a mut ratatui::Frame<'b>, area: ratatui::layout::Rect) -> Self {
Self {
area,
frame,
scroll_buffers: Vec::new(),
input_viewports: vec![InputViewport {
delta_x: 0,
delta_y: 0,
clip: area,
}],
}
}
pub fn buffer_mut(&mut self) -> &mut ratatui::buffer::Buffer {
if let Some(scroll_buffer) = self.scroll_buffers.last_mut() {
scroll_buffer
} else {
self.frame.buffer_mut()
}
}
pub fn push_scroll_buffer(&mut self, buffer: Buffer, viewport: Rect, offset: Position) {
let parent = *self.input_viewports.last().unwrap();
self.input_viewports.push(InputViewport {
delta_x: parent.delta_x + i32::from(viewport.x) - i32::from(offset.x),
delta_y: parent.delta_y + i32::from(viewport.y) - i32::from(offset.y),
clip: parent
.clip
.intersection(translated(viewport, parent.delta_x, parent.delta_y)),
});
self.scroll_buffers.push(buffer);
}
pub(crate) fn input_stamp(&self) -> crate::input::Stamp {
let viewport = self.input_viewports.last().unwrap();
let mut stamp = crate::input::draw_clock_stamp(self.area);
stamp.screen_area =
viewport
.clip
.intersection(translated(self.area, viewport.delta_x, viewport.delta_y));
stamp.delta_x = viewport.delta_x;
stamp.delta_y = viewport.delta_y;
stamp
}
pub fn pop_scroll_buffer(&mut self) -> Option<Buffer> {
let buffer = self.scroll_buffers.pop();
if buffer.is_some() {
self.input_viewports.pop();
}
buffer
}
pub fn render_widget<W: Widget>(&mut self, widget: W, area: Rect) {
widget.render(area, self.buffer_mut());
}
pub fn render_stateful_widget<W: StatefulWidget>(
&mut self,
widget: W,
area: Rect,
state: &mut W::State,
) {
widget.render(area, self.buffer_mut(), state);
}
}