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(BrailleSpinner),
Box::new(DotRing),
Box::new(ArcSweep),
Box::new(DualArc),
Box::new(Bounce),
Box::new(Pulse),
Box::new(Orbit),
Box::new(ClockHand),
Box::new(Radar),
Box::new(Ellipsis),
Box::new(GrowingArc),
Box::new(SquareRunner),
Box::new(SpinnerBars),
Box::new(HourglassFlip),
]
}
#[inline]
fn dot_center(grid: &BrailleGrid) -> (f32, f32) {
let (dw, dh) = draw::dot_dims(grid);
(dw as f32 * 0.5 - 0.5, dh as f32 * 0.5 - 0.5)
}
#[inline]
fn dot_polar(grid: &mut BrailleGrid, cx: f32, cy: f32, r: f32, theta: f32) {
let x = cx + r * theta.cos();
let y = cy + r * theta.sin();
draw::dot_i(grid, x.round() as i32, y.round() as i32);
}
fn draw_arc(
grid: &mut BrailleGrid,
cx: f32,
cy: f32,
r: f32,
theta_start: f32,
theta_end: f32,
steps: u32,
) {
if steps == 0 {
return;
}
for i in 0..=steps {
let t = i as f32 / steps as f32;
let theta = theta_start + t * (theta_end - theta_start);
dot_polar(grid, cx, cy, r, theta);
}
}
fn draw_radial(grid: &mut BrailleGrid, cx: f32, cy: f32, len: f32, theta: f32) {
let steps = (len.ceil() as u32).max(1);
for i in 0..=steps {
let r = i as f32 * len / steps as f32;
dot_polar(grid, cx, cy, r, theta);
}
}
struct BrailleSpinner;
const BRAILLE_FRAMES: [char; 8] = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧'];
impl ProgressStyle for BrailleSpinner {
fn name(&self) -> &str {
"braille-spin"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Classic ⠋⠙⠹⠸⠼⠴⠦⠧ single-cell braille glyph cycling at the center"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
let rate = 10.0 + ctx.progress * 6.0;
let frame = (ctx.time * rate) as usize % BRAILLE_FRAMES.len();
let cx = cw / 2;
let cy = ch / 2;
draw::glyph(grid, cx, cy, BRAILLE_FRAMES[frame]);
Ok(())
}
}
struct DotRing;
impl ProgressStyle for DotRing {
fn name(&self) -> &str {
"dot-ring"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Rotating comet head on a dot ring — bright lead, fading tail"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let r = (dw.min(dh) as f32 * 0.35).max(1.0);
let n: usize = 12;
let tail_len: usize = 5;
let head_angle = ctx.time * 2.5 * 2.0 * PI;
let head_idx = (head_angle / (2.0 * PI) * n as f32) as usize % n;
for i in 0..n {
let behind = (head_idx + n - i) % n;
if behind < tail_len {
let theta = 2.0 * PI * i as f32 / n as f32;
if behind == 0 || behind < tail_len / 2 + 1 {
dot_polar(grid, cx, cy, r, theta);
} else {
if i % 2 == 0 {
dot_polar(grid, cx, cy, r, theta);
}
}
}
else if i % 3 == 0 {
let theta = 2.0 * PI * i as f32 / n as f32;
dot_polar(grid, cx, cy, r, theta);
}
}
let head_theta = 2.0 * PI * head_idx as f32 / n as f32;
let hx = (cx + r * head_theta.cos()).round() as i32;
let hy = (cy + r * head_theta.sin()).round() as i32;
if hx >= 0 && hy >= 0 {
let cell_x = (hx as usize) / 2;
let cell_y = (hy as usize) / 4;
let color = ctx.palette.sample(1.0);
let (cw, ch) = grid.dimensions();
if cell_x < cw && cell_y < ch {
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct ArcSweep;
impl ProgressStyle for ArcSweep {
fn name(&self) -> &str {
"arc-sweep"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"A quarter-arc sweeping clockwise around the center"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let r = (dw.min(dh) as f32 * 0.38).max(1.0);
let arc_span = PI * 0.5; let head = ctx.time * 2.0 * PI * 0.8;
let steps = ((r * arc_span).ceil() as u32).max(2).min(32);
draw_arc(grid, cx, cy, r, head, head + arc_span, steps);
Ok(())
}
}
struct DualArc;
impl ProgressStyle for DualArc {
fn name(&self) -> &str {
"dual-arc"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Two half-arcs spinning in opposite directions on the same ring"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let r = (dw.min(dh) as f32 * 0.38).max(1.0);
let arc_span = PI * 0.55;
let t = ctx.time * 2.0 * PI * 0.7;
let steps = ((r * arc_span).ceil() as u32).max(2).min(32);
draw_arc(grid, cx, cy, r, t, t + arc_span, steps);
draw_arc(grid, cx, cy, r, -t + PI, -t + PI + arc_span, steps);
Ok(())
}
}
struct Bounce;
impl ProgressStyle for Bounce {
fn name(&self) -> &str {
"bounce"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Knight Rider: a dot bouncing left-right along the center with a fading trail"
}
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 cy = dh / 2;
let phase = (ctx.time * PI * 0.9).sin(); let head_x = ((phase * 0.5 + 0.5) * (dw.saturating_sub(1)) as f32).round() as usize;
let trail_len: i32 = 5;
let direction_sign: i32 = if phase >= 0.0 { 1 } else { -1 };
for i in 0..trail_len {
let tx = head_x as i32 - direction_sign * i;
if i == 0 {
draw::dot_i(grid, tx, cy as i32);
} else if i < trail_len / 2 + 1 {
draw::dot_i(grid, tx, cy as i32);
} else if i % 2 == 0 {
draw::dot_i(grid, tx, cy as i32);
}
}
let cell_x = head_x / 2;
let cell_y = cy / 4;
let (cw, ch) = grid.dimensions();
if cell_x < cw && cell_y < ch {
let color = ctx.palette.sample(head_x as f32 / dw.max(1) as f32);
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
Ok(())
}
}
struct Pulse;
impl ProgressStyle for Pulse {
fn name(&self) -> &str {
"pulse"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Breathing circle that expands and contracts with a sine rhythm"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let max_r = (dw.min(dh) as f32 * 0.40).max(1.0);
let breath = (ctx.time * PI * 0.7).sin() * 0.5 + 0.5; let r = max_r * (0.3 + breath * 0.7);
let steps = ((2.0 * PI * r).ceil() as u32).max(4).min(64);
draw_arc(grid, cx, cy, r, 0.0, 2.0 * PI, steps);
let (cw, ch) = grid.dimensions();
let mid_cy = ch / 2;
if ch > 0 && cw > 0 {
let color = ctx.palette.sample(breath);
draw::tint_row(grid, mid_cy, 0, cw.saturating_sub(1), color);
}
Ok(())
}
}
struct Orbit;
impl ProgressStyle for Orbit {
fn name(&self) -> &str {
"orbit"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Moon orbiting a stationary center planet"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
draw::dot_i(grid, cx.round() as i32, cy.round() as i32);
let r = (dw.min(dh) as f32 * 0.35).max(1.5);
let theta = ctx.time * 2.0 * PI * 0.6;
dot_polar(grid, cx, cy, r, theta);
for i in 1..3usize {
let trail_theta = theta - i as f32 * (PI / 8.0);
if i == 1 {
dot_polar(grid, cx, cy, r, trail_theta);
}
}
let mx = (cx + r * theta.cos()).round() as i32;
let my = (cy + r * theta.sin()).round() as i32;
if mx >= 0 && my >= 0 {
let cell_x = (mx as usize) / 2;
let cell_y = (my as usize) / 4;
let (cw, ch) = grid.dimensions();
if cell_x < cw && cell_y < ch {
let color = ctx.palette.sample(0.8);
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct ClockHand;
impl ProgressStyle for ClockHand {
fn name(&self) -> &str {
"clock-hand"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Clock-hand: a single radial spoke sweeping 360° with time"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let len = (dw.min(dh) as f32 * 0.40).max(1.0);
let theta = ctx.time * 2.0 * PI * 0.5 - PI / 2.0;
draw_radial(grid, cx, cy, len, theta);
draw::dot_i(grid, cx.round() as i32, cy.round() as i32);
Ok(())
}
}
struct Radar;
impl ProgressStyle for Radar {
fn name(&self) -> &str {
"radar"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Radar sweep: rotating spoke with a fading wedge afterglow"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let r = (dw.min(dh) as f32 * 0.40).max(1.0);
let head = ctx.time * 2.0 * PI * 0.55;
for ghost in 1..=4usize {
let fade_angle = head - ghost as f32 * (PI / 10.0);
let steps = ((r * 0.8).ceil() as u32).max(1).min(20);
for i in 0..=steps {
let t = i as f32 / steps as f32;
let gr = t * r * 0.85;
if ghost > 2 && i % 2 != 0 {
continue;
}
let gx = cx + gr * fade_angle.cos();
let gy = cy + gr * fade_angle.sin();
draw::dot_i(grid, gx.round() as i32, gy.round() as i32);
}
}
let steps = (r.ceil() as u32).max(1).min(24);
draw_radial(grid, cx, cy, r, head);
let _ = steps;
let n = 24u32;
for i in 0..n {
if i % 4 == 0 {
let theta = 2.0 * PI * i as f32 / n as f32;
dot_polar(grid, cx, cy, r, theta);
}
}
draw::dot_i(grid, cx.round() as i32, cy.round() as i32);
Ok(())
}
}
struct Ellipsis;
impl ProgressStyle for Ellipsis {
fn name(&self) -> &str {
"ellipsis"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Three-dot ellipsis cycling · ·· ··· — the classic Loading... indicator"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let phase = (ctx.time / 1.2).fract(); let lit = (phase * 4.0) as usize;
let dot_count = 3usize;
let start_x = cw.saturating_sub(dot_count) / 2;
let mid_y = ch / 2;
const DARK: char = '⠂'; const BRIGHT: char = '⣿';
for i in 0..dot_count {
let cx = (start_x + i).min(cw.saturating_sub(1));
if i < lit.min(dot_count) {
draw::glyph(grid, cx, mid_y, BRIGHT);
let color = ctx.palette.sample(i as f32 / dot_count as f32);
draw::tint_row(grid, mid_y, cx, cx, color);
} else {
draw::glyph(grid, cx, mid_y, DARK);
}
}
Ok(())
}
}
struct GrowingArc;
impl ProgressStyle for GrowingArc {
fn name(&self) -> &str {
"growing-arc"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Material-style arc that grows then shrinks as it rotates around the ring"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let r = (dw.min(dh) as f32 * 0.38).max(1.0);
let cycle = (ctx.time * 0.5).fract(); let arc_frac = if cycle < 0.5 {
cycle * 2.0
} else {
1.0 - (cycle - 0.5) * 2.0
};
let arc_span = (PI / 18.0) + arc_frac * (PI * 1.4);
let head = ctx.time * 2.0 * PI * 0.4;
let steps = ((r * arc_span).ceil() as u32).max(2).min(48);
draw_arc(grid, cx, cy, r, head, head + arc_span, steps);
let tip_theta = head + arc_span;
let tx = (cx + r * tip_theta.cos()).round() as i32;
let ty = (cy + r * tip_theta.sin()).round() as i32;
if tx >= 0 && ty >= 0 {
let cell_x = (tx as usize) / 2;
let cell_y = (ty as usize) / 4;
let (cw, ch) = grid.dimensions();
if cell_x < cw && cell_y < ch {
let color = ctx.palette.sample(1.0);
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
}
Ok(())
}
}
struct SquareRunner;
impl ProgressStyle for SquareRunner {
fn name(&self) -> &str {
"square-runner"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"A dot chasing around the perimeter of a centered square — with outline"
}
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 side = dw.min(dh).saturating_sub(2).max(1);
let half = side / 2;
let (cx, cy) = dot_center(grid);
let x0 = (cx - half as f32).round().max(0.0) as usize;
let y0 = (cy - half as f32).round().max(0.0) as usize;
draw::rect_outline(grid, x0, y0, side, side);
let perim = (side.saturating_sub(1)) * 4;
if perim == 0 {
return Ok(());
}
let pos = (ctx.time * (perim as f32) * 0.5).rem_euclid(perim as f32) as usize;
let seg = side.saturating_sub(1).max(1);
let (rx, ry) = if pos < seg {
(x0 + pos, y0)
} else if pos < 2 * seg {
(x0 + side.saturating_sub(1), y0 + (pos - seg))
} else if pos < 3 * seg {
(
x0 + side.saturating_sub(1).saturating_sub(pos - 2 * seg),
y0 + side.saturating_sub(1),
)
} else {
(
x0,
y0 + side.saturating_sub(1).saturating_sub(pos - 3 * seg),
)
};
draw::dot(grid, rx, ry);
let cell_x = rx / 2;
let cell_y = ry / 4;
let (cw, ch) = grid.dimensions();
if cell_x < cw && cell_y < ch {
let t = pos as f32 / perim.max(1) as f32;
let color = ctx.palette.sample(t);
draw::tint_row(grid, cell_y, cell_x, cell_x, color);
}
Ok(())
}
}
struct SpinnerBars;
impl ProgressStyle for SpinnerBars {
fn name(&self) -> &str {
"spinner-bars"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Ring of 8 radial spokes fading in sequence — the classic macOS throbber"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (dw, dh) = draw::dot_dims(grid);
let (cx, cy) = dot_center(grid);
let outer_r = (dw.min(dh) as f32 * 0.40).max(2.0);
let inner_r = (outer_r * 0.45).max(1.0);
let n: usize = 8;
let head_idx = (ctx.time * n as f32 * 1.5).rem_euclid(n as f32) as usize % n;
for i in 0..n {
let theta = 2.0 * PI * i as f32 / n as f32 - PI / 2.0; let behind = (head_idx + n - i) % n;
if behind > n / 2 {
continue; }
let len_frac = 1.0 - behind as f32 / (n / 2 + 1) as f32;
let spoke_outer = inner_r + (outer_r - inner_r) * len_frac;
let steps = ((spoke_outer - inner_r).ceil() as u32).max(1).min(8);
for s in 0..=steps {
let r = inner_r + (spoke_outer - inner_r) * s as f32 / steps as f32;
dot_polar(grid, cx, cy, r, theta);
}
}
Ok(())
}
}
struct HourglassFlip;
const HOURGLASS_FRAMES: [char; 4] = ['⧗', '⧗', '⧖', '⧖'];
impl ProgressStyle for HourglassFlip {
fn name(&self) -> &str {
"hourglass-flip"
}
fn theme(&self) -> &str {
"spinner"
}
fn describe(&self) -> &str {
"Hourglass glyph flipping ⧗↔⧖ with a braille-dot shimmer around it"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cw, ch) = grid.dimensions();
if cw == 0 || ch == 0 {
return Ok(());
}
let frame = (ctx.time * 1.25) as usize % HOURGLASS_FRAMES.len();
let cx = cw / 2;
let cy = ch / 2;
draw::glyph(grid, cx, cy, HOURGLASS_FRAMES[frame]);
let color = ctx.palette.sample(if frame < 2 { 0.2 } else { 0.8 });
draw::tint_row(grid, cy, cx, cx, color);
let (dw, dh) = draw::dot_dims(grid);
let dcx = dw as f32 * 0.5;
let dcy = dh as f32 * 0.5;
let r = 2.5_f32.max(1.0);
let theta = ctx.time * 2.0 * PI * 1.2;
let sx = (dcx + r * theta.cos()).round() as i32;
let sy = (dcy + r * theta.sin()).round() as i32;
let shimmer_cell_x = (sx.max(0) as usize) / 2;
let shimmer_cell_y = (sy.max(0) as usize) / 4;
if shimmer_cell_x != cx || shimmer_cell_y != cy {
draw::dot_i(grid, sx, sy);
}
Ok(())
}
}