use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Caterpillar),
Box::new(Snail),
Box::new(Inchworm),
Box::new(FishSchool),
Box::new(Snake),
Box::new(RabbitHops),
Box::new(PawPrints),
Box::new(BirdFlock),
Box::new(Turtle),
Box::new(AntMarch),
]
}
struct Caterpillar;
impl ProgressStyle for Caterpillar {
fn name(&self) -> &str {
"caterpillar"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Segmented caterpillar body undulating across the bar as it advances"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let mid = h / 2;
let head_x = (ctx.eased * w as f32) as usize;
let seg_spacing = 4usize;
let phase_offset = ctx.time * 6.0;
let mut x = 0usize;
while x < head_x {
let wave = ((x as f32 * 0.4 + phase_offset) * 1.0).sin();
let amp = ((h / 2).saturating_sub(1).max(1)) as f32 * 0.6;
let y = (mid as f32 + wave * amp).round() as usize;
let y = y.min(h - 1);
draw::dot(grid, x, y);
if x + 1 < head_x {
draw::dot(grid, x + 1, y);
}
if (x / seg_spacing) % 2 == 0 {
if y + 1 < h {
draw::dot(grid, x, y + 1);
}
if y >= 1 {
draw::dot(grid, x, y - 1);
}
}
x += seg_spacing;
}
if head_x > 0 {
let hx = head_x.min(w - 1);
let hy = mid.min(h - 1);
draw::dot(grid, hx, hy);
draw::dot_i(grid, hx as i32 + 1, hy as i32 - 1);
draw::dot_i(grid, hx as i32 + 1, hy as i32 - 2);
draw::dot_i(grid, hx as i32 + 2, hy as i32 - 1);
}
Ok(())
}
}
struct Snail;
impl ProgressStyle for Snail {
fn name(&self) -> &str {
"snail"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Snail leaving a slime trail — shell advances, trail fills behind it"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let base = (h - 1).min(h.saturating_sub(1));
let head_x = (ctx.eased * w as f32) as usize;
if head_x > 0 {
draw::hline(grid, 0, head_x.saturating_sub(1).min(w - 1), base);
let shimmer_period = 8usize;
let mut sx = 0usize;
while sx < head_x.saturating_sub(2) {
draw::dot(grid, sx, base.saturating_sub(1));
sx += shimmer_period;
}
}
let sx = head_x.min(w.saturating_sub(1));
let shell_h = (h / 2).max(2);
let shell_w = (shell_h).min(w.saturating_sub(sx));
for i in 0..=shell_w {
let t = if shell_w == 0 {
0.5
} else {
i as f32 / shell_w as f32
};
let arc_y = (base as f32 - (1.0 - (t * PI).sin() * (shell_h as f32 - 1.0)).max(0.0))
.round() as usize;
let arc_y = arc_y.max(base.saturating_sub(shell_h));
draw::dot(grid, (sx + i).min(w - 1), arc_y.min(h - 1));
}
let bob = ((ctx.time * 3.0).sin() * 0.4).round() as i32;
let inner_x = sx as i32 + shell_w as i32 / 2;
let inner_y = base as i32 - shell_h as i32 / 2 + bob;
draw::dot_i(grid, inner_x, inner_y);
let eye_x = sx as i32 + shell_w as i32 + 1;
draw::dot_i(grid, eye_x, base as i32);
draw::dot_i(grid, eye_x, base as i32 - 1);
draw::dot_i(grid, eye_x + 1, base as i32 - 2);
Ok(())
}
}
struct Inchworm;
impl ProgressStyle for Inchworm {
fn name(&self) -> &str {
"inchworm"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Inchworm bunching and stretching — eased segment spacing gives organic motion"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let mid = h / 2;
let head_x = (ctx.eased * w as f32) as usize;
let n_segs = 6usize;
let stretch_phase = ((ctx.time * 2.0).sin() * 0.5 + 0.5) as f32;
for seg in 0..=n_segs {
let raw_t = if n_segs == 0 {
0.5
} else {
seg as f32 / n_segs as f32
};
let bunched = raw_t * raw_t; let t = bunched * (1.0 - stretch_phase) + raw_t * stretch_phase;
let sx = (t * head_x as f32) as usize;
let sx = sx.min(w - 1);
let arch = (PI * raw_t).sin();
let arch_h = (arch * (h / 2) as f32).round() as usize;
let sy = mid.saturating_sub(arch_h).min(h - 1);
draw::dot(grid, sx, sy);
if seg > 0 {
let prev_t_raw = (seg - 1) as f32 / n_segs as f32;
let prev_bunched = prev_t_raw * prev_t_raw;
let prev_t = prev_bunched * (1.0 - stretch_phase) + prev_t_raw * stretch_phase;
let prev_x = (prev_t * head_x as f32) as usize;
let prev_arch = (PI * prev_t_raw).sin();
let prev_arch_h = (prev_arch * (h / 2) as f32).round() as usize;
let prev_y = mid.saturating_sub(prev_arch_h).min(h - 1);
let bx = (prev_x + sx) / 2;
let by = (prev_y + sy) / 2;
draw::dot(grid, bx.min(w - 1), by.min(h - 1));
}
}
if head_x > 0 {
let hx = head_x.min(w.saturating_sub(1));
draw::dot_i(grid, hx as i32 + 1, mid as i32);
draw::dot_i(grid, hx as i32 + 1, mid as i32 - 1);
}
Ok(())
}
}
struct FishSchool;
impl ProgressStyle for FishSchool {
fn name(&self) -> &str {
"fish-school"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"School of fish bobbing in sine-wave formation, swimming with progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let front_x = (ctx.eased * w as f32) as usize;
let n_fish = (w / 8).max(2).min(10);
let amp = (h / 2).saturating_sub(1).max(1) as f32 * 0.8;
let mid = h as f32 / 2.0;
for i in 0..n_fish {
let lag = i as f32 / n_fish as f32;
let fish_x = (front_x as f32 * (1.0 - lag * 0.35)) as usize;
let fish_x = fish_x.min(w.saturating_sub(2));
let phase = i as f32 * (PI * 2.0 / n_fish as f32);
let bob_y = mid + amp * (ctx.time * 4.0 + phase).sin();
let fy = (bob_y.round() as usize).min(h - 1);
draw::dot(grid, fish_x, fy);
if fish_x + 1 < w {
draw::dot(grid, fish_x + 1, fy);
}
if fish_x >= 1 {
draw::dot(grid, fish_x - 1, fy.saturating_sub(1).min(h - 1));
draw::dot(grid, fish_x - 1, (fy + 1).min(h - 1));
}
}
let (cells_w, cells_h) = grid.dimensions();
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cy in 0..cells_h {
for cx in 0..filled_cells.min(cells_w) {
let t = if filled_cells <= 1 {
0.5
} else {
cx as f32 / filled_cells as f32
};
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct Snake;
impl ProgressStyle for Snake {
fn name(&self) -> &str {
"snake"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Snake slithering — sine-wave lateral offset along a horizontal advance"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let mid = h as f32 / 2.0;
let amp = (h as f32 / 2.0 - 1.0).max(0.5);
let head_x = (ctx.eased * w as f32) as usize;
let phase = ctx.time * 5.0;
for x in 0..head_x.min(w) {
let freq = 0.25 + (x as f32 / w.max(1) as f32) * 0.25;
let y = mid + amp * (x as f32 * freq + phase).sin();
let y = (y.round() as usize).min(h - 1);
draw::dot(grid, x, y);
if x % 3 == 0 {
let y2 = (y + 1).min(h - 1);
draw::dot(grid, x, y2);
}
}
if head_x > 0 {
let hx = head_x.min(w - 1);
let hy_f = mid + amp * (hx as f32 * 0.5 + phase).sin();
let hy = (hy_f.round() as usize).min(h - 1);
let tongue = ((ctx.time * 3.0).sin() > 0.4) as usize;
if tongue > 0 {
draw::dot_i(grid, hx as i32 + 1, hy as i32 - 1);
draw::dot_i(grid, hx as i32 + 2, hy as i32 - 2);
draw::dot_i(grid, hx as i32 + 2, hy as i32);
} else {
draw::dot_i(grid, hx as i32 + 1, hy as i32);
}
draw::dot(grid, hx, hy.saturating_sub(1));
}
Ok(())
}
}
struct RabbitHops;
impl ProgressStyle for RabbitHops {
fn name(&self) -> &str {
"rabbit-hops"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Rabbit hopping across in parabolic arcs — each hop a discrete progress chunk"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n_hops = 5usize;
let hop_w = w / n_hops.max(1);
let base = h - 1;
let peak = (h * 2 / 3).max(1);
let total_progress = ctx.eased;
let hop_index_f = total_progress * n_hops as f32;
let current_hop = (hop_index_f as usize).min(n_hops - 1);
let hop_frac = hop_index_f.fract();
for h_idx in 0..current_hop {
let gx = h_idx * hop_w + hop_w / 2;
draw::dot(grid, gx.min(w - 1), base);
}
let hop_start_x = current_hop * hop_w;
let hop_end_x = (hop_start_x + hop_w).min(w);
let t = if ctx.progress >= 1.0 {
hop_frac
} else {
hop_frac
};
let arc = -4.0 * peak as f32 * t * (t - 1.0);
let rx = hop_start_x + (hop_frac * (hop_end_x - hop_start_x) as f32) as usize;
let ry_raw = base as f32 - arc;
let ry = (ry_raw.round() as usize).min(h - 1);
let rx = rx.min(w.saturating_sub(2));
draw::dot(grid, rx, ry);
if rx + 1 < w {
draw::dot(grid, rx + 1, ry);
}
draw::dot_i(grid, rx as i32, ry as i32 - 1);
draw::dot_i(grid, rx as i32 + 1, ry as i32 - 1);
draw::dot_i(grid, rx as i32, ry as i32 - 2);
draw::dot_i(grid, rx as i32 + 2, ry as i32 - 2);
draw::dot(grid, rx.min(w - 1), base);
Ok(())
}
}
struct PawPrints;
impl ProgressStyle for PawPrints {
fn name(&self) -> &str {
"paw-prints"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Paw prints appearing one by one, alternating left/right offset across the bar"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n_prints = 8usize;
let visible = (ctx.eased * n_prints as f32).ceil() as usize;
let mid = h / 2;
let step_w = w / n_prints.max(1);
let offset = (h / 4).max(1);
for i in 0..visible.min(n_prints) {
let px = i * step_w + step_w / 2;
let px = px.min(w.saturating_sub(2));
let (py, small_off) = if i % 2 == 0 {
(mid.saturating_sub(offset), 1usize)
} else {
((mid + offset).min(h.saturating_sub(2)), 0usize)
};
draw::dot(grid, px, py);
draw::dot(grid, (px + 1).min(w - 1), py);
draw::dot(grid, px, (py + 1).min(h - 1));
draw::dot(grid, (px + 1).min(w - 1), (py + 1).min(h - 1));
let toe_y = py.saturating_sub(1);
draw::dot(grid, px.saturating_sub(1), toe_y);
draw::dot(grid, px, toe_y.saturating_sub(small_off));
draw::dot(grid, (px + 1).min(w - 1), toe_y);
let t = if n_prints <= 1 {
1.0
} else {
i as f32 / (n_prints - 1) as f32
};
let color = ctx.palette.sample(t);
let (cells_w, cells_h) = grid.dimensions();
let cell_x = (px / 2).min(cells_w.saturating_sub(1));
for cy in 0..cells_h {
draw::tint_row(
grid,
cy,
cell_x,
(cell_x + 1).min(cells_w.saturating_sub(1)),
color,
);
}
}
Ok(())
}
}
struct BirdFlock;
impl ProgressStyle for BirdFlock {
fn name(&self) -> &str {
"bird-flock"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"V-formation bird flock sweeping across the bar, wings flapping with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let mid = h / 2;
let leader_x = (ctx.eased * w as f32) as usize;
let flap = (ctx.time * 5.0).sin();
let wing_spread = ((h / 4).max(1)) as f32;
let n_side = 3usize; let v_step_x = 3usize; let v_step_y = 1usize; let lx = leader_x.min(w.saturating_sub(1));
let ly = mid.min(h - 1);
draw_bird(grid, lx, ly, flap, wing_spread, w, h);
for side in 0..n_side {
let offset_x = (side + 1) * v_step_x;
let offset_y = (side + 1) * v_step_y;
let lag = side as f32 * 0.4;
let flap_bird = (ctx.time * 5.0 - lag).sin();
let lx_left = leader_x.saturating_sub(offset_x);
let ly_left = mid.saturating_sub(offset_y).min(h - 1);
draw_bird(
grid,
lx_left.min(w.saturating_sub(1)),
ly_left,
flap_bird,
wing_spread * 0.7,
w,
h,
);
let lx_right = leader_x.saturating_sub(offset_x);
let ly_right = (mid + offset_y).min(h - 1);
draw_bird(
grid,
lx_right.min(w.saturating_sub(1)),
ly_right,
flap_bird,
wing_spread * 0.7,
w,
h,
);
}
let (cells_w, cells_h) = grid.dimensions();
let swept = (ctx.eased * cells_w as f32) as usize;
for cy in 0..cells_h {
for cx in 0..swept.min(cells_w) {
let t = if swept <= 1 {
0.0
} else {
cx as f32 / (swept - 1) as f32
};
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
fn draw_bird(
grid: &mut BrailleGrid,
bx: usize,
by: usize,
flap: f32,
wing_spread: f32,
_w: usize,
h: usize,
) {
let wing_h = (flap * wing_spread * 0.5).round() as i32;
draw::dot_i(grid, bx as i32, by as i32);
draw::dot_i(grid, bx as i32 - 1, by as i32 + wing_h);
draw::dot_i(
grid,
bx as i32 - 2,
by as i32 + wing_h.abs().min((h / 2) as i32),
);
draw::dot_i(grid, bx as i32 + 1, by as i32 + wing_h);
draw::dot_i(
grid,
bx as i32 + 2,
by as i32 + wing_h.abs().min((h / 2) as i32),
);
}
struct Turtle;
impl ProgressStyle for Turtle {
fn name(&self) -> &str {
"turtle"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Turtle shell dome that fills from bottom up as progress increases"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let shell_w = (w * 4 / 5).max(4).min(w);
let x0 = w.saturating_sub(shell_w) / 2;
let base = h - 1;
for xi in 0..=shell_w {
let t = if shell_w == 0 {
0.5
} else {
xi as f32 / shell_w as f32
};
let norm = 2.0 * t - 1.0;
let ellipse_y = (h as f32 * (1.0 - norm * norm).sqrt()).min(h as f32 - 1.0);
let top_y = base.saturating_sub(ellipse_y.round() as usize);
draw::dot(grid, (x0 + xi).min(w - 1), top_y.min(h - 1));
}
draw::hline(grid, x0, (x0 + shell_w).min(w - 1), base);
let fill_height = (ctx.eased * h as f32).round() as usize;
for xi in 0..=shell_w {
let t = if shell_w == 0 {
0.5
} else {
xi as f32 / shell_w as f32
};
let norm = 2.0 * t - 1.0;
let dome_height = (h as f32 * (1.0 - norm * norm).sqrt()).round() as usize;
let col_fill = fill_height.min(dome_height);
if col_fill > 0 {
let y_top = base.saturating_sub(col_fill);
draw::vline(grid, (x0 + xi).min(w - 1), y_top, base);
}
}
let bob = ((ctx.time * 1.5).sin() * 0.5) as i32;
for row in (0..h).step_by(3) {
for col in (x0..x0 + shell_w).step_by(4) {
draw::dot_i(grid, col as i32, row as i32 + bob);
}
}
draw::dot_i(grid, x0 as i32 - 1, base as i32);
draw::dot_i(grid, x0 as i32 - 2, base as i32);
draw::dot_i(grid, (x0 + shell_w) as i32 + 1, base as i32);
draw::dot_i(grid, x0 as i32 + 1, base as i32 + 1);
draw::dot_i(grid, (x0 + shell_w) as i32 - 1, base as i32 + 1);
Ok(())
}
}
struct AntMarch;
impl ProgressStyle for AntMarch {
fn name(&self) -> &str {
"ant-march"
}
fn theme(&self) -> &str {
"animals"
}
fn describe(&self) -> &str {
"Ants marching in single file, carrying the load — legs animate with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (w, h) = draw::dot_dims(grid);
if w == 0 || h == 0 {
return Ok(());
}
let n_ants = (w / 7).max(1).min(8);
let base = (h - 1).min(h.saturating_sub(1));
let head_x = (ctx.eased * w as f32) as usize;
let spacing = if n_ants <= 1 {
head_x.max(1)
} else {
head_x / n_ants.max(1)
};
let leg_phase = ctx.time * 8.0;
for i in 0..n_ants {
let ant_x = if spacing == 0 { 0 } else { i * spacing };
if ant_x >= head_x.max(1) {
break;
}
let ant_x = ant_x.min(w.saturating_sub(3));
let ant_leg_phase = leg_phase + i as f32 * PI / n_ants as f32;
let leg_up = (ant_leg_phase.sin() > 0.0) as usize;
draw::dot(grid, (ant_x + 2).min(w - 1), base.saturating_sub(2));
draw::dot_i(grid, ant_x as i32 + 1, base as i32 - 3);
draw::dot_i(grid, ant_x as i32 + 3, base as i32 - 3 + leg_up as i32);
draw::dot(grid, (ant_x + 2).min(w - 1), base.saturating_sub(1));
draw::dot(grid, (ant_x + 2).min(w - 1), base);
for leg in 0..3usize {
let leg_y_off = if (leg + leg_up) % 2 == 0 { 0i32 } else { 1i32 };
draw::dot_i(
grid,
ant_x as i32 + 1,
base as i32 - leg as i32 + leg_y_off - 1,
);
draw::dot_i(
grid,
ant_x as i32 + 3,
base as i32 - leg as i32 + leg_y_off - 1,
);
}
}
let (cells_w, cells_h) = grid.dimensions();
let ant_cells = (ctx.eased * cells_w as f32) as usize;
for cy in 0..cells_h {
for cx in 0..ant_cells.min(cells_w) {
let t = if ant_cells <= 1 {
0.0
} else {
cx as f32 / (ant_cells - 1) as f32
};
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}