use super::{Area, Shapes, unit};
use crate::colour::Style;
use crate::surface::{Cells, Surface};
const EIGHTHS: [char; 7] = ['▏', '▎', '▍', '▌', '▋', '▊', '▉'];
fn put(surface: &mut Surface<Cells>, x: i64, y: i64, character: char, style: Style) {
if (0..i64::from(u32::MAX)).contains(&x) && (0..i64::from(u32::MAX)).contains(&y) {
surface.put(x as u32, y as u32, character, style);
}
}
fn span(from: i64, to: i64, limit: u32) -> core::ops::Range<i64> {
from.max(0)..to.min(i64::from(limit))
}
fn frame(surface: &mut Surface<Cells>, area: Area, corners: [char; 4], style: Style) {
if area.w == 0 || area.h == 0 {
return;
}
let (cols, rows) = surface.size();
let (left, top) = (area.left(), area.top());
let (right, bottom) = (area.right() - 1, area.bottom() - 1);
if area.h == 1 {
for x in span(left, right + 1, cols) {
put(surface, x, top, '─', style);
}
return;
}
if area.w == 1 {
for y in span(top, bottom + 1, rows) {
put(surface, left, y, '│', style);
}
return;
}
for x in span(left + 1, right, cols) {
put(surface, x, top, '─', style);
put(surface, x, bottom, '─', style);
}
for y in span(top + 1, bottom, rows) {
put(surface, left, y, '│', style);
put(surface, right, y, '│', style);
}
put(surface, left, top, corners[0], style);
put(surface, right, top, corners[1], style);
put(surface, left, bottom, corners[2], style);
put(surface, right, bottom, corners[3], style);
}
fn inside(dx: i64, dy: i64, rx: i64, ry: i64) -> bool {
let (dx, dy, rx, ry) = (dx * 2, dy * 2, rx * 2 + 1, ry * 2 + 1);
dx * dx * ry * ry + dy * dy * rx * rx <= rx * rx * ry * ry
}
fn ellipse(
surface: &mut Surface<Cells>,
centre: (i32, i32),
radius: u32,
fill: bool,
style: Style,
) {
let (cols, rows) = surface.size();
let (cx, cy) = (i64::from(centre.0), i64::from(centre.1));
let (rx, ry) = (i64::from(radius) * 2, i64::from(radius));
for y in span(cy - ry, cy + ry + 1, rows) {
for x in span(cx - rx, cx + rx + 1, cols) {
let (dx, dy) = (x - cx, y - cy);
if !inside(dx, dy, rx, ry) {
continue;
}
let edge = !inside(dx - 1, dy, rx, ry)
|| !inside(dx + 1, dy, rx, ry)
|| !inside(dx, dy - 1, rx, ry)
|| !inside(dx, dy + 1, rx, ry);
if fill {
put(surface, x, y, '█', style);
} else if edge {
put(surface, x, y, '•', style);
}
}
}
}
impl Shapes for Surface<Cells> {
type Ink = Style;
fn stroke_rect(&mut self, area: Area, ink: Style) {
frame(self, area, ['┌', '┐', '└', '┘'], ink);
}
fn fill_rect(&mut self, area: Area, ink: Style) {
let (cols, rows) = self.size();
for y in span(area.top(), area.bottom(), rows) {
for x in span(area.left(), area.right(), cols) {
put(self, x, y, '█', ink);
}
}
}
fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Style) {
let (cols, rows) = self.size();
let (fx, fy) = (i64::from(from.0), i64::from(from.1));
let (tx, ty) = (i64::from(to.0), i64::from(to.1));
let glyph = if fy == ty {
'─'
} else if fx == tx {
'│'
} else if (tx > fx) == (ty > fy) {
'╲'
} else {
'╱'
};
let Some(((mut x, mut y), (x1, y1))) = super::clip_segment((fx, fy), (tx, ty), cols, rows)
else {
return;
};
let (dx, dy) = ((x1 - x).abs(), -(y1 - y).abs());
let (sx, sy) = (if x < x1 { 1 } else { -1 }, if y < y1 { 1 } else { -1 });
let mut error = dx + dy;
loop {
put(self, x, y, glyph, ink);
if x == x1 && y == y1 {
break;
}
let doubled = 2 * error;
if doubled >= dy {
error += dy;
x += sx;
}
if doubled <= dx {
error += dx;
y += sy;
}
}
}
fn bar(&mut self, area: Area, fraction: f32, fill: Style, track: Style) {
let (cols, rows) = self.size();
let eighths = (unit(fraction) * area.w as f32 * 8.0 + 0.5) as i64;
for y in span(area.top(), area.bottom(), rows) {
for x in span(area.left(), area.right(), cols) {
let here = (eighths - (x - area.left()) * 8).clamp(0, 8);
match here {
8 => put(self, x, y, '█', fill),
0 => put(self, x, y, '░', track),
partial => put(
self,
x,
y,
EIGHTHS[partial as usize - 1],
fill.with_bg(track.bg),
),
}
}
}
}
fn rounded_rect(&mut self, area: Area, radius: u32, ink: Style) {
let corners = if radius == 0 {
['┌', '┐', '└', '┘']
} else {
['╭', '╮', '╰', '╯']
};
frame(self, area, corners, ink);
}
fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Style) {
self.fill_rect(area, ink);
if radius == 0 || area.w < 2 || area.h < 2 {
return;
}
let (right, bottom) = (area.right() - 1, area.bottom() - 1);
put(self, area.left(), area.top(), '▗', ink);
put(self, right, area.top(), '▖', ink);
put(self, area.left(), bottom, '▝', ink);
put(self, right, bottom, '▘', ink);
}
fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Style) {
ellipse(self, cell_centre(centre), cell_radius(radius), false, ink);
}
fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Style) {
ellipse(self, cell_centre(centre), cell_radius(radius), true, ink);
}
fn label(&mut self, x: i32, y: i32, text: &str, ink: Style) -> u32 {
crate::surface::write_text_clipped(self, i64::from(x), i64::from(y), text, ink)
}
}
fn cell_centre((x, y): (f32, f32)) -> (i32, i32) {
let whole = |v: f32| {
if v.is_nan() {
0
} else {
libm::floorf(v).clamp(i32::MIN as f32, i32::MAX as f32) as i32
}
};
(whole(x), whole(y))
}
fn cell_radius(radius: f32) -> u32 {
if radius.is_nan() {
0
} else {
libm::roundf(radius).clamp(0.0, u32::MAX as f32) as u32
}
}