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(FourierSquare),
Box::new(FourierSawtooth),
Box::new(FourierTriangle),
Box::new(Epicycle),
Box::new(Lissajous),
Box::new(StandingWave),
Box::new(Interference),
Box::new(Chladni),
Box::new(BeatFrequency),
Box::new(WavePacket),
Box::new(Spectrum),
]
}
#[inline]
fn y_to_dot(norm: f32, h: usize) -> i32 {
let row = ((1.0 - (norm * 0.5 + 0.5)) * h as f32) as i32;
row.clamp(0, h as i32 - 1)
}
struct FourierSquare;
impl ProgressStyle for FourierSquare {
fn name(&self) -> &str {
"fourier-square"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Fourier square-wave synthesis: Gibbs ringing grows visible as harmonics unlock 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 harmonics = 1 + (ctx.eased * 12.0).floor() as usize;
let phase = ctx.time * 2.0 * PI * 0.3;
let base = h / 2;
draw::hline(grid, 0, w.saturating_sub(1), base);
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let theta = (xi as f32 / w as f32) * 4.0 * PI + phase;
let mut val: f32 = 0.0;
for k in 1..=harmonics {
let n = (2 * k - 1) as f32;
val += (n * theta).sin() / n;
}
val *= 4.0 / PI;
let val_n = val.clamp(-1.0, 1.0);
let dy = y_to_dot(val_n, h);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let lo = py.min(dy);
let hi = py.max(dy);
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
let (cw, ch) = grid.dimensions();
let filled_cells = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled_cells.min(cw) {
let t = if filled_cells <= 1 {
0.5
} else {
cx as f32 / (filled_cells - 1) as f32
};
let col = ctx.palette.sample(t);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct FourierSawtooth;
impl ProgressStyle for FourierSawtooth {
fn name(&self) -> &str {
"fourier-sawtooth"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Fourier sawtooth synthesis: each harmonic adds a finer diagonal ramp until the teeth appear"
}
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 harmonics = 1 + (ctx.eased * 11.0).floor() as usize;
let phase = ctx.time * 2.0 * PI * 0.25;
let mid = (h / 2) as i32;
let amp = (h as f32 * 0.45).max(1.0);
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let theta = (xi as f32 / w as f32) * 4.0 * PI + phase;
let mut val: f32 = 0.0;
for k in 1..=harmonics {
let kf = k as f32;
let sign = if k % 2 == 1 { 1.0_f32 } else { -1.0_f32 };
val += sign * (kf * theta).sin() / kf;
}
val *= 2.0 / PI;
let dy = (mid - (val * amp) as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let (lo, hi) = (py.min(dy), py.max(dy));
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
let (cw, ch) = grid.dimensions();
let head_cell = (ctx.eased * cw as f32).round() as usize;
for cy in 0..ch {
let col = ctx.palette.sample(ctx.eased);
draw::tint_row(grid, cy, 0, head_cell.min(cw.saturating_sub(1)), col);
}
Ok(())
}
}
struct FourierTriangle;
impl ProgressStyle for FourierTriangle {
fn name(&self) -> &str {
"fourier-triangle"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Fourier triangle-wave: smoother convergence than square/sawtooth — peaks sharpen gradually"
}
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 harmonics = 1 + (ctx.eased * 10.0).floor() as usize;
let phase = ctx.time * 2.0 * PI * 0.2;
let mid = (h / 2) as i32;
let amp = (h as f32 * 0.44).max(1.0);
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let theta = (xi as f32 / w as f32) * 4.0 * PI + phase;
let mut val: f32 = 0.0;
for k in 0..harmonics {
let kf = k as f32;
let n = 2.0 * kf + 1.0;
let sign = if k % 2 == 0 { 1.0_f32 } else { -1.0_f32 };
val += sign * (n * theta).sin() / (n * n);
}
val *= 8.0 / (PI * PI);
let dy = (mid - (val * amp) as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let (lo, hi) = (py.min(dy), py.max(dy));
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled.min(cw) {
let t = cx as f32 / cw as f32;
let col = ctx.palette.sample(t);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}
struct Epicycle;
impl ProgressStyle for Epicycle {
fn name(&self) -> &str {
"epicycle"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Epicycle chain: N rotating vectors sum to trace a square-wave path; watch the circles spin"
}
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_vec = 1 + (ctx.eased * 7.0).floor() as usize; let omega = ctx.time * 1.8;
let cx = w as f32 / 2.0;
let cy = h as f32 / 2.0;
let r_base = (h as f32 * 0.35).max(1.0);
let steps = w * 2;
let mut prev: Option<(i32, i32)> = None;
for si in 0..steps {
let t = si as f32 / steps as f32; let phase_offset = t * 2.0 * PI;
let mut tip_x = cx;
let mut tip_y = cy;
for k in 1..=n_vec {
let kf = k as f32;
let radius = r_base / kf;
let angle = (2 * k - 1) as f32 * (omega + phase_offset);
tip_x += radius * angle.cos();
tip_y += radius * angle.sin();
}
let px = ((tip_x / w as f32) * w as f32) as i32;
let py = tip_y as i32;
draw::dot_i(grid, px, py);
if let Some((lx, ly)) = prev {
let dx = (px - lx).abs();
if dx > 0 {
for step in 0..=dx {
let ix = lx + (px - lx) * step / dx.max(1);
let iy = ly + (py - ly) * step / dx.max(1);
draw::dot_i(grid, ix, iy);
}
}
draw::dot_i(grid, px, py);
}
prev = Some((px, py));
}
let angle0 = omega;
let r0 = r_base;
let circle_pts = 48usize;
for i in 0..circle_pts {
let a = angle0 + (i as f32 / circle_pts as f32) * 2.0 * PI;
let px = (cx + r0 * a.cos()) as i32;
let py = (cy + r0 * a.sin()) as i32;
draw::dot_i(grid, px, py);
}
{
let mut tip_x = cx;
let mut tip_y = cy;
for k in 1..=n_vec {
let kf = k as f32;
let radius = r_base / kf;
let angle = (2 * k - 1) as f32 * omega;
let nx = tip_x + radius * angle.cos();
let ny = tip_y + radius * angle.sin();
let steps2 = (radius.max(1.0) as usize).max(2);
for s in 0..=steps2 {
let frac = s as f32 / steps2 as f32;
let ax = (tip_x + (nx - tip_x) * frac) as i32;
let ay = (tip_y + (ny - tip_y) * frac) as i32;
draw::dot_i(grid, ax, ay);
}
tip_x = nx;
tip_y = ny;
}
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx2 in 0..filled.min(cw) {
let t = cx2 as f32 / cw as f32;
for cy2 in 0..ch {
draw::tint_row(grid, cy2, cx2, cx2, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct Lissajous;
impl ProgressStyle for Lissajous {
fn name(&self) -> &str {
"lissajous"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Lissajous figure 3:2 — phase delta sweeps with progress, time rotates the knot in place"
}
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 cx = (w as f32 - 1.0) / 2.0;
let cy = (h as f32 - 1.0) / 2.0;
let rx = cx * 0.92;
let ry = cy * 0.92;
let a = 3.0_f32;
let b = 2.0_f32;
let delta = ctx.eased * 2.0 * PI;
let tau_off = ctx.time * 0.5;
let steps = (w * h * 2).max(256);
let period = 2.0 * PI; let mut prev: Option<(i32, i32)> = None;
for si in 0..steps {
let tau = (si as f32 / steps as f32) * period + tau_off;
let lx = rx * (a * tau + delta).sin();
let ly = ry * (b * tau).sin();
let px = (cx + lx) as i32;
let py = (cy + ly) as i32;
draw::dot_i(grid, px, py);
if let Some((ox, oy)) = prev {
let steps2 = (((px - ox).abs() + (py - oy).abs()) as usize).max(1);
for s in 1..steps2 {
let f = s as f32 / steps2 as f32;
let ix = (ox as f32 + (px - ox) as f32 * f) as i32;
let iy = (oy as f32 + (py - oy) as f32 * f) as i32;
draw::dot_i(grid, ix, iy);
}
}
prev = Some((px, py));
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx2 in 0..filled.min(cw) {
let t = cx2 as f32 / cw as f32;
for cy2 in 0..ch {
draw::tint_row(grid, cy2, cx2, cx2, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct StandingWave;
impl ProgressStyle for StandingWave {
fn name(&self) -> &str {
"standing-wave"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Standing wave: fixed nodes, breathing antinodes — mode unlocks as progress rises"
}
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 mode = 1 + (ctx.eased * 6.0).floor() as usize; let k = mode as f32 * PI / w as f32; let omega = 2.5 * PI; let amp = h as f32 * 0.40;
let mid = (h / 2) as i32;
draw::hline(grid, 0, w.saturating_sub(1), mid as usize);
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let xf = xi as f32;
let val = 2.0 * amp * (k * xf).sin() * (omega * ctx.time).cos();
let dy = (mid - val as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let (lo, hi) = (py.min(dy), py.max(dy));
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
for n in 0..=mode {
let node_x = (n as f32 / mode as f32 * w as f32) as usize;
if node_x < w {
let tick = (h / 8).max(1);
let y0 = mid as usize;
draw::vline(
grid,
node_x,
y0.saturating_sub(tick),
(y0 + tick).min(h - 1),
);
}
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled.min(cw) {
let t = cx as f32 / cw as f32;
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct Interference;
impl ProgressStyle for Interference {
fn name(&self) -> &str {
"interference"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Two-source interference: constructive/destructive fringe bands sweep 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 wf = w as f32;
let hf = h as f32;
let sep = (ctx.eased * wf * 0.6 + wf * 0.1).min(wf - 1.0);
let src1_x = (wf / 2.0 - sep / 2.0).max(0.0);
let src2_x = (wf / 2.0 + sep / 2.0).min(wf - 1.0);
let src_y = hf / 2.0;
let lambda = wf / 4.0; let k = 2.0 * PI / lambda.max(1.0);
let omega = 2.0 * PI * 1.2;
let threshold = 0.5_f32;
for yi in 0..h {
let yf = yi as f32;
for xi in 0..w {
let xf = xi as f32;
let r1 = ((xf - src1_x).powi(2) + (yf - src_y).powi(2)).sqrt();
let r2 = ((xf - src2_x).powi(2) + (yf - src_y).powi(2)).sqrt();
let s1 = (k * r1 - omega * ctx.time).sin();
let s2 = (k * r2 - omega * ctx.time).sin();
let intensity = (s1 + s2) * 0.5; if intensity.abs() > threshold {
draw::dot(grid, xi, yi);
}
}
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled.min(cw) {
let t = cx as f32 / cw as f32;
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct Chladni;
impl ProgressStyle for Chladni {
fn name(&self) -> &str {
"chladni"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Chladni nodal lines: sand settles on vibration nodes — pattern changes 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 modes: [(f32, f32); 5] = [(1.0, 2.0), (1.0, 3.0), (2.0, 3.0), (2.0, 5.0), (3.0, 4.0)];
let idx = (ctx.eased * 4.999) as usize;
let (n, m) = modes[idx.min(4)];
let blend = (ctx.time * 0.15).sin() * 0.08;
let n = n + blend;
let m = m - blend;
let threshold = 0.25_f32;
for yi in 0..h {
let yf = yi as f32 / h as f32; for xi in 0..w {
let xf = xi as f32 / w as f32; let val = (n * PI * xf).cos() * (m * PI * yf).cos()
- (m * PI * xf).cos() * (n * PI * yf).cos();
if val.abs() < threshold {
draw::dot(grid, xi, yi);
}
}
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled.min(cw) {
let t = cx as f32 / cw as f32;
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct BeatFrequency;
impl ProgressStyle for BeatFrequency {
fn name(&self) -> &str {
"beat-frequency"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Beat frequency: two close sinusoids interfere to create a slowly pulsing envelope"
}
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) as i32;
let amp = (h as f32 * 0.44).max(1.0);
let f1 = 8.0_f32; let df = ctx.eased * 2.0 + 0.05; let f2 = f1 + df;
let phase_shift = ctx.time * 2.0 * PI * 0.4;
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let xn = xi as f32 / w as f32; let theta = xn * 2.0 * PI + phase_shift;
let val = (f1 * theta).sin() + (f2 * theta).sin();
let val_n = val * 0.5; let dy = (mid - (val_n * amp) as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let (lo, hi) = (py.min(dy), py.max(dy));
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
for xi in 0..w {
let xn = xi as f32 / w as f32;
let theta = xn * 2.0 * PI + phase_shift;
let env = 2.0 * ((df * theta / 2.0).cos()).abs() * 0.5 * amp;
let top_y = (mid - env as i32).clamp(0, h as i32 - 1);
let bot_y = (mid + env as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, top_y);
draw::dot_i(grid, xi as i32, bot_y);
}
let (cw, ch) = grid.dimensions();
let filled = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled.min(cw) {
let t = cx as f32 / cw as f32;
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, ctx.palette.sample(t));
}
}
Ok(())
}
}
struct WavePacket;
impl ProgressStyle for WavePacket {
fn name(&self) -> &str {
"wave-packet"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Gaussian wave packet: a quantum-style probability envelope travels 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 wf = w as f32;
let mid = (h / 2) as i32;
let amp = (h as f32 * 0.44).max(1.0);
let x0 = ctx.eased * wf;
let sigma = (wf / 5.0).max(1.0);
let k = 12.0 * PI / wf;
let phi = ctx.time * 4.0 * PI;
let mut prev_y: Option<i32> = None;
for xi in 0..w {
let xf = xi as f32;
let dx = xf - x0;
let envelope = (-(dx * dx) / (2.0 * sigma * sigma)).exp();
let carrier = (k * dx + phi).sin();
let val = envelope * carrier;
let dy = (mid - (val * amp) as i32).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, dy);
if let Some(py) = prev_y {
let (lo, hi) = (py.min(dy), py.max(dy));
for yy in lo..=hi {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_y = Some(dy);
}
let mut prev_env: Option<(i32, i32)> = None;
for xi in 0..w {
let xf = xi as f32;
let dx = xf - x0;
let envelope = (-(dx * dx) / (2.0 * sigma * sigma)).exp();
let ey = (envelope * amp) as i32;
let top = (mid - ey).clamp(0, h as i32 - 1);
let bot = (mid + ey).clamp(0, h as i32 - 1);
draw::dot_i(grid, xi as i32, top);
draw::dot_i(grid, xi as i32, bot);
if let Some((pt, pb)) = prev_env {
let (lt, ht) = (pt.min(top), pt.max(top));
let (lb, hb) = (pb.min(bot), pb.max(bot));
for yy in lt..=ht {
draw::dot_i(grid, xi as i32, yy);
}
for yy in lb..=hb {
draw::dot_i(grid, xi as i32, yy);
}
}
prev_env = Some((top, bot));
}
let (cw, ch) = grid.dimensions();
let centre_cell = (ctx.eased * cw as f32).round() as usize;
let sigma_cells = (cw / 5).max(1);
for cx in 0..cw {
let dist = if cx > centre_cell {
(cx - centre_cell) as f32
} else {
(centre_cell - cx) as f32
};
let env_c = (-(dist * dist) / (2.0 * sigma_cells as f32 * sigma_cells as f32)).exp();
if env_c > 0.05 {
let col = ctx.palette.sample(cx as f32 / cw as f32);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
}
Ok(())
}
}
struct Spectrum;
impl ProgressStyle for Spectrum {
fn name(&self) -> &str {
"spectrum"
}
fn theme(&self) -> &str {
"waves"
}
fn describe(&self) -> &str {
"Fourier spectrum equalizer: frequency bins light up left-to-right as progress fills them"
}
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_bins = w;
let n_filled = (ctx.eased * n_bins as f32).round() as usize;
if h >= 1 {
draw::hline(grid, 0, w.saturating_sub(1), h - 1);
}
for bin in 0..n_bins {
let bin_f = (bin + 1) as f32;
let spectral = (1.0 / bin_f.sqrt())
* (1.0 + 0.4 * (bin_f * 0.7).sin())
* (1.0 + 0.2 * (bin_f * 1.3 + ctx.time * 2.5).sin().abs());
let amplitude = if bin < n_filled {
spectral
} else {
spectral * 0.08
};
let bar_h = (amplitude * h as f32 * 0.85).round() as usize;
let bar_h = bar_h.clamp(0, h);
let y0 = h.saturating_sub(bar_h);
for y in y0..h {
draw::dot(grid, bin, y);
}
}
let (cw, ch) = grid.dimensions();
let filled_cells = (ctx.eased * cw as f32).round() as usize;
for cx in 0..filled_cells.min(cw) {
let t = cx as f32 / cw as f32;
let col = ctx.palette.sample(t);
for cy in 0..ch {
draw::tint_row(grid, cy, cx, cx, col);
}
}
Ok(())
}
}