use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
fn plot_circle(grid: &mut BrailleGrid, cx: f32, cy: f32, r: f32) {
if r < 0.5 {
draw::dot_i(grid, cx.round() as i32, cy.round() as i32);
return;
}
let steps = ((2.0 * PI * r).ceil() as usize * 2).max(8).min(2048);
for i in 0..steps {
let angle = 2.0 * PI * i as f32 / steps as f32;
let px = cx + r * angle.cos();
let py = cy + r * angle.sin();
draw::dot_i(grid, px.round() as i32, py.round() as i32);
}
}
fn plot_line(grid: &mut BrailleGrid, x0: f32, y0: f32, x1: f32, y1: f32) {
let dx = x1 - x0;
let dy = y1 - y0;
let steps = (dx.abs().max(dy.abs()).ceil() as usize).max(1).min(4096);
for i in 0..=steps {
let t = i as f32 / steps as f32;
let px = x0 + dx * t;
let py = y0 + dy * t;
draw::dot_i(grid, px.round() as i32, py.round() as i32);
}
}
fn hex_offset(ring_angle_index: usize, radius: f32) -> (f32, f32) {
let a = ring_angle_index as f32 * PI / 3.0;
(radius * a.cos(), radius * a.sin())
}
fn seed_centres(cx: f32, cy: f32, r: f32) -> [(f32, f32); 7] {
let mut out = [(0f32, 0f32); 7];
out[0] = (cx, cy);
for i in 0..6 {
let (dx, dy) = hex_offset(i, r);
out[i + 1] = (cx + dx, cy + dy);
}
out
}
fn flower_centres(cx: f32, cy: f32, r: f32) -> Vec<(f32, f32)> {
let mut v: Vec<(f32, f32)> = Vec::with_capacity(19);
for c in seed_centres(cx, cy, r) {
v.push(c);
}
for i in 0..6 {
let a = i as f32 * PI / 3.0 + PI / 6.0;
let d = 3f32.sqrt() * r;
v.push((cx + d * a.cos(), cy + d * a.sin()));
}
for i in 0..6 {
let (dx, dy) = hex_offset(i, 2.0 * r);
v.push((cx + dx, cy + dy));
}
v
}
fn fruit_centres(cx: f32, cy: f32, r: f32) -> Vec<(f32, f32)> {
let mut v: Vec<(f32, f32)> = Vec::with_capacity(13);
v.push((cx, cy));
for i in 0..6 {
let (dx, dy) = hex_offset(i, r);
v.push((cx + dx, cy + dy));
}
for i in 0..6 {
let (dx, dy) = hex_offset(i, 2.0 * r);
v.push((cx + dx, cy + dy));
}
v
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(VesicaPiscis),
Box::new(SeedOfLife),
Box::new(FlowerOfLife),
Box::new(EggOfLife),
Box::new(FruitOfLife),
Box::new(MetatronsCube),
Box::new(GermOfLife),
Box::new(TripodOfLife),
Box::new(TreeOfLife),
Box::new(TetraGrid),
Box::new(TorusFlower),
]
}
struct VesicaPiscis;
impl ProgressStyle for VesicaPiscis {
fn name(&self) -> &str {
"vesica-piscis"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Two circles overlap to reveal the sacred lens; arc segments appear with eased progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) * 0.45).max(1.0);
let sep = r * ctx.eased;
let lx = cx - sep / 2.0;
let rx = cx + sep / 2.0;
let breathe = 1.0 + 0.04 * (ctx.time * 1.2).sin();
let rr = r * breathe;
plot_circle(grid, lx, cy, rr);
if ctx.eased > 0.05 {
plot_circle(grid, rx, cy, rr);
}
let (cw, ch) = grid.dimensions();
let mid_cell = cw / 2;
for cy_cell in 0..ch {
if mid_cell > 0 {
draw::tint_row(
grid,
cy_cell,
0,
mid_cell.saturating_sub(1),
ctx.palette.start,
);
draw::tint_row(
grid,
cy_cell,
mid_cell,
cw.saturating_sub(1),
ctx.palette.end,
);
}
}
Ok(())
}
}
struct SeedOfLife;
impl ProgressStyle for SeedOfLife {
fn name(&self) -> &str {
"seed-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Seven circles of the Seed of Life appear petal-by-petal as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) * 0.38).max(1.0);
let rot = ctx.time * 0.25;
let reveal = (ctx.eased * 7.0).ceil() as usize;
let centres = seed_centres(cx, cy, r);
for (i, &(px, py)) in centres.iter().enumerate().take(reveal.min(7)) {
let (ox, oy) = (px - cx, py - cy);
let rx_c = ox * rot.cos() - oy * rot.sin() + cx;
let ry_c = ox * rot.sin() + oy * rot.cos() + cy;
plot_circle(grid, rx_c, ry_c, r);
let t = i as f32 / 6.0;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let cell_x = (rx_c / 2.0).round() as usize;
let cell_y = (ry_c / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct FlowerOfLife;
impl ProgressStyle for FlowerOfLife {
fn name(&self) -> &str {
"flower-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"All 19 overlapping circles of the Flower of Life bloom with progress, bounded by an outer ring"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) / 3.2).max(1.0);
let centres = flower_centres(cx, cy, r);
let total = centres.len(); let reveal = (ctx.eased * total as f32).ceil() as usize;
for (i, &(px, py)) in centres.iter().enumerate().take(reveal.min(total)) {
plot_circle(grid, px, py, r);
let t = i as f32 / (total - 1).max(1) as f32;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let cell_x = (px / 2.0).round() as usize;
let cell_y = (py / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
if ctx.eased >= 0.95 {
plot_circle(grid, cx, cy, 2.0 * r);
}
Ok(())
}
}
struct EggOfLife;
impl ProgressStyle for EggOfLife {
fn name(&self) -> &str {
"egg-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Seven separated circles in egg-of-life formation; radius pulses with time as progress fills"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) * 0.22).max(1.0);
let spacing = r * 2.2;
let reveal = (ctx.eased * 7.0).ceil() as usize;
let pulse = 1.0 + 0.06 * (ctx.time * 1.8).sin();
let mut centres = [(0f32, 0f32); 7];
centres[0] = (cx, cy);
for i in 0..6 {
let (dx, dy) = hex_offset(i, spacing);
centres[i + 1] = (cx + dx, cy + dy);
}
for (i, &(px, py)) in centres.iter().enumerate().take(reveal.min(7)) {
plot_circle(grid, px, py, r * pulse);
draw::dot_i(grid, px.round() as i32, py.round() as i32);
let t = i as f32 / 6.0;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let cell_x = (px / 2.0).round() as usize;
let cell_y = (py / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct FruitOfLife;
impl ProgressStyle for FruitOfLife {
fn name(&self) -> &str {
"fruit-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"13 circles of the Fruit of Life; connecting lines drawn between all centres with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) / 2.5).max(1.0);
let cr = (r * 0.18).max(1.0);
let centres = fruit_centres(cx, cy, r);
let total = centres.len();
let circle_frac = (ctx.eased * 2.0).min(1.0);
let line_frac = ((ctx.eased - 0.5) * 2.0).max(0.0).min(1.0);
let reveal_circles = (circle_frac * total as f32).ceil() as usize;
for (i, &(px, py)) in centres.iter().enumerate().take(reveal_circles.min(total)) {
plot_circle(grid, px, py, cr);
let t = i as f32 / (total - 1).max(1) as f32;
draw::tint_row(
grid,
(py / 4.0) as usize,
(px / 2.0) as usize,
(px / 2.0) as usize,
ctx.palette.sample(t),
);
}
let all_pairs: Vec<(usize, usize)> = (0..total)
.flat_map(|a| (a + 1..total).map(move |b| (a, b)))
.collect();
let reveal_lines = (line_frac * all_pairs.len() as f32).round() as usize;
for &(a, b) in all_pairs.iter().take(reveal_lines) {
let (ax, ay) = centres[a];
let (bx, by) = centres[b];
plot_line(grid, ax, ay, bx, by);
}
Ok(())
}
}
struct MetatronsCube;
impl ProgressStyle for MetatronsCube {
fn name(&self) -> &str {
"metatrons-cube"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Metatron's Cube: 13 nodes with all 78 connecting edges revealed progressively"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) / 2.4).max(1.0);
let rot = ctx.time * 0.15;
let raw = fruit_centres(cx, cy, r);
let centres: Vec<(f32, f32)> = raw
.iter()
.map(|&(px, py)| {
let ox = px - cx;
let oy = py - cy;
(
ox * rot.cos() - oy * rot.sin() + cx,
ox * rot.sin() + oy * rot.cos() + cy,
)
})
.collect();
let total = centres.len();
let all_pairs: Vec<(usize, usize)> = (0..total)
.flat_map(|a| (a + 1..total).map(move |b| (a, b)))
.collect();
let reveal = (ctx.eased * all_pairs.len() as f32).round() as usize;
for &(a, b) in all_pairs.iter().take(reveal) {
let (ax, ay) = centres[a];
let (bx, by) = centres[b];
let t = a as f32 / (total - 1).max(1) as f32;
let _ = t; plot_line(grid, ax, ay, bx, by);
}
let cr = 1.5f32.max(r * 0.12);
for &(px, py) in ¢res {
plot_circle(grid, px, py, cr);
}
let (cw, ch) = grid.dimensions();
for cy_cell in 0..ch {
for cx_cell in 0..cw {
let t = cx_cell as f32 / cw.max(1) as f32;
draw::tint_row(grid, cy_cell, cx_cell, cx_cell, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct GermOfLife;
impl ProgressStyle for GermOfLife {
fn name(&self) -> &str {
"germ-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Germ of Life: interior arc-petals of 7 overlapping circles fill in as progress rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) * 0.38).max(1.0);
let centres = seed_centres(cx, cy, r);
let reveal_petals = (ctx.eased * 6.0).ceil() as usize;
plot_circle(grid, cx, cy, r);
for petal in 0..reveal_petals.min(6) {
let (ox1, oy1) = (centres[petal + 1].0, centres[petal + 1].1);
let next = ((petal + 1) % 6) + 1;
let (ox2, oy2) = (centres[next].0, centres[next].1);
let steps = 256usize;
for i in 0..steps {
let angle = 2.0 * PI * i as f32 / steps as f32;
let px = ox1 + r * angle.cos();
let py = oy1 + r * angle.sin();
let dc = (px - cx).hypot(py - cy);
if dc <= r + 0.5 {
draw::dot_i(grid, px.round() as i32, py.round() as i32);
}
}
for i in 0..steps {
let angle = 2.0 * PI * i as f32 / steps as f32;
let px = ox2 + r * angle.cos();
let py = oy2 + r * angle.sin();
let d1 = (px - ox1).hypot(py - oy1);
if d1 <= r + 0.5 {
draw::dot_i(grid, px.round() as i32, py.round() as i32);
}
}
let t = petal as f32 / 5.0;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let pcx = (ox1 / 2.0).round() as usize;
let pcy = (oy1 / 4.0).round() as usize;
if pcx < cw && pcy < ch {
draw::tint_row(grid, pcy, pcx, pcx, color);
}
}
Ok(())
}
}
struct TripodOfLife;
impl ProgressStyle for TripodOfLife {
fn name(&self) -> &str {
"tripod-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Tripod of Life: three interlocking circles with a central 3-arm spoke revealed by eased"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) * 0.42).max(1.0);
let rot = ctx.time * 0.3;
let mut circle_centres = [(0f32, 0f32); 3];
for i in 0..3 {
let a = rot + i as f32 * 2.0 * PI / 3.0;
circle_centres[i] = (cx + r * 0.5 * a.cos(), cy + r * 0.5 * a.sin());
}
let circle_frac = (ctx.eased * 2.0).min(1.0);
let arm_frac = ((ctx.eased - 0.5) * 2.0).max(0.0).min(1.0);
let reveal_circles = (circle_frac * 3.0).ceil() as usize;
for i in 0..reveal_circles.min(3) {
let (px, py) = circle_centres[i];
plot_circle(grid, px, py, r);
let color = ctx.palette.sample(i as f32 / 2.0);
let (cw, ch) = grid.dimensions();
let cell_x = (px / 2.0).round() as usize;
let cell_y = (py / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
for i in 0..3 {
let a = rot + i as f32 * 2.0 * PI / 3.0;
let arm_r = r * arm_frac;
let ex = cx + arm_r * a.cos();
let ey = cy + arm_r * a.sin();
plot_line(grid, cx, cy, ex, ey);
}
if ctx.eased > 0.1 {
draw::dot_i(grid, cx.round() as i32, cy.round() as i32);
}
Ok(())
}
}
struct TreeOfLife;
impl ProgressStyle for TreeOfLife {
fn name(&self) -> &str {
"tree-of-life"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Kabbalistic Tree of Life: 10 sephirot nodes with 22 paths revealed as eased rises"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let sx = (dw as f32 / 4.5).min(dh as f32 / 6.5).max(1.0);
let sy = sx;
let nodes: [(f32, f32); 10] = [
(0.0, -3.0), (-1.0, -2.0), (1.0, -2.0), (-1.0, -1.0), (1.0, -1.0), (0.0, 0.0), (-1.0, 1.0), (1.0, 1.0), (0.0, 2.0), (0.0, 3.0), ];
let dot_nodes: Vec<(f32, f32)> = nodes
.iter()
.map(|&(x, y)| (cx + x * sx, cy + y * sy))
.collect();
let paths: [(usize, usize); 22] = [
(0, 1),
(0, 2),
(0, 5), (1, 2),
(1, 3),
(1, 5), (2, 4),
(2, 5), (3, 4),
(3, 5),
(3, 6), (4, 5),
(4, 7), (5, 6),
(5, 7),
(5, 8), (6, 7),
(6, 8), (7, 8), (8, 9), (1, 4),
(2, 3), ];
let reveal = (ctx.eased * paths.len() as f32).round() as usize;
for (i, &(a, b)) in paths.iter().enumerate().take(reveal) {
let (ax, ay) = dot_nodes[a];
let (bx, by) = dot_nodes[b];
plot_line(grid, ax, ay, bx, by);
let t = i as f32 / (paths.len() - 1).max(1) as f32;
let _ = t;
}
let nr = (sx * 0.25).max(1.0);
for (i, &(px, py)) in dot_nodes.iter().enumerate() {
plot_circle(grid, px, py, nr);
let t = i as f32 / 9.0;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let cell_x = (px / 2.0).round() as usize;
let cell_y = (py / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct TetraGrid;
impl ProgressStyle for TetraGrid {
fn name(&self) -> &str {
"64-tetra-grid"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"64-tetrahedron grid: equilateral triangular lattice revealed by progress, sweeping in from left"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let cell_size = (dw.min(dh) as f32 / 8.0).max(2.0);
let ax = cell_size;
let ay = 0.0f32;
let bx = cell_size * 0.5;
let by = cell_size * (3f32.sqrt() / 2.0);
let range = 6i32;
let mut points: Vec<(f32, f32)> = Vec::new();
for i in -range..=range {
for j in -range..=range {
let px = cx + i as f32 * ax + j as f32 * bx;
let py = cy + i as f32 * ay + j as f32 * by;
if px >= 0.0 && px < dw as f32 && py >= 0.0 && py < dh as f32 {
points.push((px, py));
}
}
}
points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let reveal = (ctx.eased * points.len() as f32).round() as usize;
for &(px, py) in points.iter().take(reveal) {
let neighbors: [(f32, f32); 6] = [
(ax, ay),
(-ax, -ay),
(bx, by),
(-bx, -by),
(ax - bx, ay - by),
(-(ax - bx), -(ay - by)),
];
draw::dot_i(grid, px.round() as i32, py.round() as i32);
for (dx, dy) in neighbors {
let nx = px + dx;
let ny = py + dy;
if nx >= 0.0 && nx < dw as f32 && ny >= 0.0 && ny < dh as f32 {
let idx = points.partition_point(|&(qx, _)| qx < nx - 0.5);
let in_revealed = points[..idx.min(reveal)]
.iter()
.any(|&(qx, qy)| (qx - nx).abs() < 1.0 && (qy - ny).abs() < 1.0);
if in_revealed {
plot_line(grid, px, py, nx, ny);
}
}
}
}
let (cw, ch) = grid.dimensions();
for cy_cell in 0..ch {
for cx_cell in 0..cw {
let t = cx_cell as f32 / cw.max(1) as f32;
draw::tint_row(grid, cy_cell, cx_cell, cx_cell, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct TorusFlower;
impl ProgressStyle for TorusFlower {
fn name(&self) -> &str {
"torus-flower"
}
fn theme(&self) -> &str {
"floweroflife"
}
fn describe(&self) -> &str {
"Flower-of-Life circles warped onto a torus surface; the whole pattern rolls with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
if dw == 0 || dh == 0 {
return Ok(());
}
let cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let r = (cx.min(cy) / 3.2).max(1.0);
let big_r = cx.min(cy) * 0.55;
let small_r = cx.min(cy) * 0.2;
let phi = ctx.time * 0.5;
let raw = flower_centres(cx, cy, r);
let total = raw.len();
let reveal = (ctx.eased * total as f32).ceil() as usize;
for (i, &(px, py)) in raw.iter().enumerate().take(reveal.min(total)) {
let theta = (py - cy).atan2(px - cx);
let dist_frac = (px - cx).hypot(py - cy) / (2.0 * r).max(1.0);
let torus_x_offset = big_r
* (theta + phi).cos()
* (1.0 - small_r / big_r * (dist_frac * PI * 2.0 + phi).cos());
let torus_y_offset = small_r * (dist_frac * PI * 2.0 + phi).sin();
let warp = 0.3;
let wpx = (1.0 - warp) * px + warp * (cx + torus_x_offset * (r / big_r.max(1.0)));
let wpy = (1.0 - warp) * py + warp * (cy + torus_y_offset * 2.0);
let depth = 1.0 + 0.25 * (dist_frac * PI * 2.0 + phi).cos();
let cr = (r * depth).max(1.0);
plot_circle(grid, wpx, wpy, cr);
let t = i as f32 / (total - 1).max(1) as f32;
let color = ctx.palette.sample(t);
let (cw, ch) = grid.dimensions();
let cell_x = (wpx / 2.0).round() as usize;
let cell_y = (wpy / 4.0).round() as usize;
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}