scrin 0.1.82

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
use crate::core::buffer::Buffer;
use crate::core::color::Color;
use crate::core::rect::Rect;
use crate::widgets::Widget;

pub struct CanvasContext {
    x_offset: i32,
    y_offset: i32,
    scale_x: f64,
    scale_y: f64,
}

impl CanvasContext {
    pub fn new(area: Rect, x_range: (f64, f64), y_range: (f64, f64)) -> Self {
        let dx = x_range.1 - x_range.0;
        let dy = y_range.1 - y_range.0;
        Self {
            x_offset: area.x as i32,
            y_offset: area.y as i32,
            scale_x: if dx == 0.0 {
                1.0
            } else {
                area.width as f64 / dx
            },
            scale_y: if dy == 0.0 {
                1.0
            } else {
                area.height as f64 / dy
            },
        }
    }

    pub fn map_x(&self, x: f64) -> i32 {
        (x * self.scale_x) as i32 + self.x_offset
    }

    pub fn map_y(&self, y: f64) -> i32 {
        (y * self.scale_y) as i32 + self.y_offset
    }

    pub fn draw_char(&self, buffer: &mut Buffer, x: f64, y: f64, ch: char, color: Color) {
        let px = self.map_x(x);
        let py = self.map_y(y);
        if px >= 0 && py >= 0 {
            let ux = px as usize;
            let uy = py as usize;
            if ux < buffer.width && uy < buffer.height {
                buffer.set(
                    ux,
                    uy,
                    crate::core::buffer::Cell {
                        ch,
                        fg: color,
                        bg: None,
                        bold: false,
                        italic: false,
                        underlined: false,
                    },
                );
            }
        }
    }

    pub fn draw_line(
        &self,
        buffer: &mut Buffer,
        x0: f64,
        y0: f64,
        x1: f64,
        y1: f64,
        ch: char,
        color: Color,
    ) {
        let dx = (x1 - x0).abs();
        let dy = (y1 - y0).abs();
        let sx = if x0 < x1 { 1.0 } else { -1.0 };
        let sy = if y0 < y1 { 1.0 } else { -1.0 };
        let mut err = dx - dy;
        let mut x = x0;
        let mut y = y0;

        loop {
            self.draw_char(buffer, x, y, ch, color);
            if (x - x1).abs() < 0.5 && (y - y1).abs() < 0.5 {
                break;
            }
            let e2 = 2.0 * err;
            if e2 > -dy {
                err -= dy;
                x += sx;
            }
            if e2 < dx {
                err += dx;
                y += sy;
            }
        }
    }

    pub fn draw_rect(
        &self,
        buffer: &mut Buffer,
        x: f64,
        y: f64,
        w: f64,
        h: f64,
        ch: char,
        color: Color,
    ) {
        self.draw_line(buffer, x, y, x + w, y, ch, color);
        self.draw_line(buffer, x + w, y, x + w, y + h, ch, color);
        self.draw_line(buffer, x + w, y + h, x, y + h, ch, color);
        self.draw_line(buffer, x, y + h, x, y, ch, color);
    }

    pub fn draw_circle(
        &self,
        buffer: &mut Buffer,
        cx: f64,
        cy: f64,
        r: f64,
        ch: char,
        color: Color,
    ) {
        let steps = (r * 20.0) as u32;
        for i in 0..steps {
            let angle = (i as f64 / steps as f64) * std::f64::consts::TAU;
            let x = cx + r * angle.cos();
            let y = cy + r * angle.sin();
            self.draw_char(buffer, x, y, ch, color);
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CanvasBackground {
    None,
    Grid(char, Color),
}

pub struct Canvas {
    pub x_range: (f64, f64),
    pub y_range: (f64, f64),
    pub background: CanvasBackground,
    pub paint: Box<dyn Fn(&mut Buffer, &CanvasContext)>,
}

impl Canvas {
    pub fn new<F>(x_range: (f64, f64), y_range: (f64, f64), paint: F) -> Self
    where
        F: Fn(&mut Buffer, &CanvasContext) + 'static,
    {
        Self {
            x_range,
            y_range,
            background: CanvasBackground::None,
            paint: Box::new(paint),
        }
    }

    pub fn with_background(mut self, bg: CanvasBackground) -> Self {
        self.background = bg;
        self
    }
}

impl Widget for Canvas {
    fn render(&self, buffer: &mut Buffer, area: Rect) {
        if self.background != CanvasBackground::None {
            if let CanvasBackground::Grid(ch, color) = self.background {
                for y in area.y..area.bottom() {
                    for x in area.x..area.right() {
                        buffer.set(
                            x as usize,
                            y as usize,
                            crate::core::buffer::Cell {
                                ch,
                                fg: color.dim(0.3),
                                bg: None,
                                bold: false,
                                italic: false,
                                underlined: false,
                            },
                        );
                    }
                }
            }
        }

        let ctx = CanvasContext::new(area, self.x_range, self.y_range);
        (self.paint)(buffer, &ctx);
    }
}