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);
}
}