use std::fmt;
use std::rc::Rc;
use rdom_core::{Dom, NodeId};
use crate::ext::TuiExt;
use crate::render::{Buffer, Rect, Style};
pub type PaintFn = dyn Fn(&Dom<TuiExt>, &mut RenderContext<'_>);
#[derive(Clone)]
pub struct CanvasPaint(Rc<PaintFn>);
impl CanvasPaint {
pub(crate) fn new<F>(f: F) -> Self
where
F: Fn(&Dom<TuiExt>, &mut RenderContext<'_>) + 'static,
{
CanvasPaint(Rc::new(f))
}
pub(crate) fn call(&self, dom: &Dom<TuiExt>, ctx: &mut RenderContext<'_>) {
(self.0)(dom, ctx);
}
}
impl fmt::Debug for CanvasPaint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("CanvasPaint(…)")
}
}
impl PartialEq for CanvasPaint {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for CanvasPaint {}
pub fn set_paint<F>(dom: &mut crate::TuiDom, canvas: NodeId, f: F)
where
F: Fn(&Dom<TuiExt>, &mut RenderContext<'_>) + 'static,
{
let paint = CanvasPaint::new(f);
if let Some(ext) = dom.node_mut(canvas).ext_mut() {
ext.canvas_paint = Some(paint);
}
}
pub fn clear_paint(dom: &mut crate::TuiDom, canvas: NodeId) {
if let Some(ext) = dom.node_mut(canvas).ext_mut() {
ext.canvas_paint = None;
}
}
pub fn has_paint(dom: &crate::TuiDom, canvas: NodeId) -> bool {
dom.node(canvas)
.ext()
.map(|e| e.canvas_paint.is_some())
.unwrap_or(false)
}
pub struct RenderContext<'a> {
buffer: &'a mut Buffer,
origin_x: i32,
origin_y: i32,
width: u16,
height: u16,
clip: Rect,
}
impl<'a> RenderContext<'a> {
pub(crate) fn new(
buffer: &'a mut Buffer,
origin_x: i32,
origin_y: i32,
width: u16,
height: u16,
clip: Rect,
) -> Self {
Self {
buffer,
origin_x,
origin_y,
width,
height,
clip,
}
}
pub fn for_test(buffer: &'a mut Buffer, area: Rect) -> Self {
Self::new(
buffer,
area.x as i32,
area.y as i32,
area.width,
area.height,
area,
)
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn set(&mut self, x: u16, y: u16, ch: char, style: Style) {
if x >= self.width || y >= self.height {
return;
}
let ax = self.origin_x + x as i32;
let ay = self.origin_y + y as i32;
if !self.in_clip(ax, ay) {
return;
}
self.buffer.set_char(ax as u16, ay as u16, ch, style);
}
pub fn text(&mut self, x: u16, y: u16, s: &str, style: Style) {
if y >= self.height || x >= self.width {
return;
}
let ax = self.origin_x + x as i32;
let ay = self.origin_y + y as i32;
let canvas_budget = self.width - x;
let clip_right = self.clip.right() as i32;
let clip_budget = (clip_right - ax).max(0) as u16;
let max = canvas_budget.min(clip_budget);
if max == 0 {
return;
}
if ay < self.clip.y as i32 || ay >= self.clip.bottom() as i32 {
return;
}
if ax < self.clip.x as i32 {
return;
}
self.buffer.set_stringn(ax as u16, ay as u16, s, max, style);
}
pub fn rect(&mut self, x: u16, y: u16, w: u16, h: u16, style: Style) {
let x_end = x.saturating_add(w).min(self.width);
let y_end = y.saturating_add(h).min(self.height);
for yy in y..y_end {
for xx in x..x_end {
self.set(xx, yy, ' ', style);
}
}
}
pub fn fill(&mut self, style: Style) {
self.rect(0, 0, self.width, self.height, style);
}
pub fn clear(&mut self) {
self.fill(Style::new());
}
pub fn sub<'b>(&'b mut self, x: u16, y: u16, w: u16, h: u16) -> RenderContext<'b> {
let clamped_w = self.width.saturating_sub(x).min(w);
let clamped_h = self.height.saturating_sub(y).min(h);
RenderContext::new(
&mut *self.buffer,
self.origin_x + x as i32,
self.origin_y + y as i32,
clamped_w,
clamped_h,
self.clip,
)
}
fn in_clip(&self, x: i32, y: i32) -> bool {
x >= self.clip.x as i32
&& x < self.clip.right() as i32
&& y >= self.clip.y as i32
&& y < self.clip.bottom() as i32
}
}
#[cfg(test)]
mod tests;