use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
#[inline]
fn hash(n: u32) -> u32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x.wrapping_mul(2_246_822_519)
}
#[inline]
fn hashf(n: u32) -> f32 {
(hash(n) % 1000) as f32 / 1000.0
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(MatrixRain),
Box::new(NeonScanline),
Box::new(DataPackets),
Box::new(GlitchBar),
Box::new(TerminalTyper),
Box::new(HexFill),
Box::new(SignalBars),
Box::new(DownloadStream),
Box::new(BinaryCounter),
Box::new(Heartbeat),
Box::new(CircuitTrace),
]
}
struct MatrixRain;
impl ProgressStyle for MatrixRain {
fn name(&self) -> &str {
"matrix-rain"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Matrix digital rain: column density rises 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 (cells_w, cells_h) = grid.dimensions();
let active_cols = ((ctx.eased * w as f32) as usize).min(w);
for col in 0..active_cols {
let phase = hashf(col as u32 * 7 + 1);
let speed = 0.4 + 0.6 * hashf(col as u32 * 13 + 3);
let fall_t = (ctx.time * speed + phase).fract();
let head = (fall_t * h as f32) as usize;
let tail_len = (h / 3).max(2);
for i in 0..tail_len {
let y = if head >= i { head - i } else { h + head - i };
if y < h {
draw::dot(grid, col, y);
}
}
let cell_x = col / 2;
if cell_x < cells_w {
let t = col as f32 / active_cols.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cell_x, cell_x, color);
}
}
}
Ok(())
}
}
struct NeonScanline;
impl ProgressStyle for NeonScanline {
fn name(&self) -> &str {
"neon-scanline"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Neon vertical scanline with palette glow, eased to 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 (cells_w, cells_h) = grid.dimensions();
let head = ((ctx.eased * w as f32) as usize).min(w.saturating_sub(1));
let mid = h / 2;
draw::hline(grid, 0, head, mid);
let beam_w = (w / 20).max(1);
for offset in 0..beam_w {
if head < offset {
break;
}
let x = head - offset;
let reach = (h as f32 * (1.0 - offset as f32 / beam_w as f32)) as usize;
let y0 = mid.saturating_sub(reach / 2);
let y1 = (mid + reach / 2).min(h.saturating_sub(1));
draw::vline(grid, x, y0, y1);
}
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = if filled_cells <= 1 {
0.0
} else {
cx as f32 / (filled_cells - 1) as f32
};
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct DataPackets;
impl ProgressStyle for DataPackets {
fn name(&self) -> &str {
"data-packets"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Discrete data packets stream right; flow rate scales 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 (cells_w, cells_h) = grid.dimensions();
let packet_w = (w / 8).max(2);
let gap = (w / 12).max(1);
let stride = packet_w + gap;
let n_packets = (w / stride).max(1);
let active = (ctx.eased * n_packets as f32).ceil() as usize;
for p in 0..active.min(n_packets) {
let phase = p as f32 / n_packets as f32;
let t_raw = (ctx.time * 0.5 + phase).fract();
let t_eased = 1.0 - (1.0 - t_raw).powi(3);
let x0 = (t_eased * (w + packet_w) as f32) as i32 - packet_w as i32;
let row_h = (h / 2).max(1);
let y0 = (h - row_h) / 2;
for dy in 0..row_h {
for dx in 0..packet_w as i32 {
draw::dot_i(grid, x0 + dx, (y0 + dy) as i32);
}
}
let t_color = p as f32 / n_packets.max(1) as f32;
let color = ctx.palette.sample(t_color);
let cx0 = (x0 / 2).max(0) as usize;
let cx1 = ((x0 + packet_w as i32) / 2).clamp(0, cells_w as i32 - 1) as usize;
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx0, cx1, color);
}
}
Ok(())
}
}
struct GlitchBar;
impl ProgressStyle for GlitchBar {
fn name(&self) -> &str {
"glitch"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Solid fill with periodic horizontal glitch displacements"
}
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 (cells_w, cells_h) = grid.dimensions();
let filled = ((ctx.eased * w as f32) as usize).min(w);
let epoch = (ctx.time * 3.0) as u32;
for y in 0..h {
let row_hash = hash(y as u32 * 31 + epoch * 997);
let glitch_prob = hashf(row_hash);
let shift: i32 = if glitch_prob < 0.08 {
let mag = (hashf(row_hash ^ 0xDEAD) * (w as f32 / 8.0)) as i32;
if row_hash & 1 == 0 {
mag
} else {
-mag
}
} else {
0
};
for x in 0..filled {
draw::dot_i(grid, x as i32 + shift, y as i32);
}
}
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct TerminalTyper;
impl ProgressStyle for TerminalTyper {
fn name(&self) -> &str {
"terminal-typer"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Cursor types across the bar; cursor blinks, trail is filled"
}
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 (cells_w, cells_h) = grid.dimensions();
let head = ((ctx.eased * w as f32) as usize).min(w.saturating_sub(1));
if head > 0 {
draw::fill_rect(grid, 0, 0, head, h);
}
let blink_on = (ctx.time * 2.0).fract() > 0.5;
if blink_on && head < w {
draw::vline(grid, head, 0, h.saturating_sub(1));
if head + 1 < w {
draw::vline(grid, head + 1, 0, h.saturating_sub(1));
}
}
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct HexFill;
impl ProgressStyle for HexFill {
fn name(&self) -> &str {
"hex-fill"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Hex-cell blocks toggle on sequentially as progress 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 (cells_w, cells_h) = grid.dimensions();
let n_cells = 16usize.min(w / 2);
let cell_w = w / n_cells.max(1);
let lit = (ctx.eased * n_cells as f32) as usize;
for i in 0..n_cells {
let x0 = i * cell_w;
let bw = cell_w.saturating_sub(1).max(1);
if i < lit {
draw::fill_rect(grid, x0, 0, bw, h);
} else if i == lit {
let flicker = ((ctx.time * 8.0).sin() * 0.5 + 0.5).clamp(0.0, 1.0);
let bh = (flicker * h as f32) as usize;
let y0 = h.saturating_sub(bh);
draw::fill_rect(grid, x0, y0, bw, bh);
} else {
draw::rect_outline(grid, x0, 0, bw.max(2), h.max(2));
}
if i <= lit {
let t = i as f32 / n_cells.max(1) as f32;
let color = ctx.palette.sample(t);
let cx0 = (x0 / 2).min(cells_w.saturating_sub(1));
let cx1 = ((x0 + bw) / 2).min(cells_w.saturating_sub(1));
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx0, cx1, color);
}
}
}
Ok(())
}
}
struct SignalBars;
impl ProgressStyle for SignalBars {
fn name(&self) -> &str {
"signal-bars"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Rising WiFi-style signal bars that fill 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 (cells_w, cells_h) = grid.dimensions();
let n_bars = 8usize;
let bar_w = (w / n_bars / 2).max(1);
let gap = (w / n_bars).saturating_sub(bar_w).max(1);
let stride = bar_w + gap;
let lit_count = (ctx.eased * n_bars as f32).round() as usize;
for i in 0..n_bars {
let x0 = i * stride;
if x0 >= w {
break;
}
let base_frac = (i + 1) as f32 / n_bars as f32;
let pulse = if i < lit_count {
1.0 + 0.05 * (ctx.time * 2.0 * PI + i as f32 * 0.5).sin()
} else {
1.0
};
let bar_h = (base_frac * h as f32 * pulse).round() as usize;
let bar_h = bar_h.min(h);
let y0 = h.saturating_sub(bar_h);
if i < lit_count {
draw::fill_rect(grid, x0, y0, bar_w, bar_h);
let t = i as f32 / n_bars.max(1) as f32;
let color = ctx.palette.sample(t);
let cx = (x0 / 2).min(cells_w.saturating_sub(1));
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
} else {
if bar_h >= 2 && bar_w >= 1 {
draw::rect_outline(grid, x0, y0, bar_w.max(2), bar_h.max(2));
}
}
}
Ok(())
}
}
struct DownloadStream;
impl ProgressStyle for DownloadStream {
fn name(&self) -> &str {
"download-stream"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Moving buffer window slides over a download track"
}
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 (cells_w, cells_h) = grid.dimensions();
let filled = ((ctx.eased * w as f32) as usize).min(w);
let mid = h / 2;
for x in 0..filled {
if x % 3 == 0 {
draw::dot(grid, x, mid);
}
}
let win_w = (w / 6).max(2);
if filled > win_w {
let travel = filled - win_w;
let win_x = ((ctx.time * 0.8).fract() * travel as f32) as usize;
let win_x = win_x.min(travel);
let win_h = h;
draw::fill_rect(grid, win_x, 0, win_w, win_h);
}
draw::hline(grid, 0, w.saturating_sub(1), h.saturating_sub(1));
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct BinaryCounter;
impl ProgressStyle for BinaryCounter {
fn name(&self) -> &str {
"binary-counter"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Progress displayed as a live binary counter in dot columns"
}
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 (cells_w, cells_h) = grid.dimensions();
let n_bits = (w / 4).clamp(4, 16);
let max_val = (1u32 << n_bits).saturating_sub(1);
let val = (ctx.eased * max_val as f32).round() as u32;
let bit_w = w / n_bits;
let bit_w = bit_w.max(1);
for bit in 0..n_bits {
let bit_idx = n_bits - 1 - bit;
let on = (val >> bit_idx) & 1 == 1;
let x0 = bit * bit_w;
let bw = bit_w.saturating_sub(1).max(1);
if on {
draw::fill_rect(grid, x0, 0, bw, h);
let t = bit as f32 / n_bits.max(1) as f32;
let color = ctx.palette.sample(t);
let cx = (x0 / 2).min(cells_w.saturating_sub(1));
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
} else {
draw::dot(grid, x0, h.saturating_sub(1));
}
}
let lsb_x = (n_bits - 1) * bit_w;
if (ctx.time * 8.0).fract() > 0.5 {
draw::vline(grid, lsb_x, 0, h.saturating_sub(1));
}
Ok(())
}
}
struct Heartbeat;
impl ProgressStyle for Heartbeat {
fn name(&self) -> &str {
"heartbeat"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"EKG heartbeat line pulses in real time; trace advances 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 (cells_w, cells_h) = grid.dimensions();
let filled = ((ctx.eased * w as f32) as usize).min(w);
let base = (h * 2 / 3).min(h.saturating_sub(1));
draw::hline(grid, 0, filled.saturating_sub(1), base);
let spike_w = (w / 6).max(4);
let phase = ctx.time.fract();
let spike_center = if filled > spike_w {
let travel = filled - spike_w;
let scroll = (phase * travel as f32) as usize;
scroll + spike_w / 2
} else {
filled / 2
};
let peak_h = (h as f32 * 0.85) as usize;
for dx in 0..spike_w {
let x = spike_center.saturating_sub(spike_w / 2) + dx;
if x >= w {
break;
}
let t = dx as f32 / spike_w.max(1) as f32;
let y_offset: i32 = if t < 0.25 {
let rise = t / 0.25;
-((rise * peak_h as f32) as i32)
} else if t < 0.5 {
let fall = (t - 0.25) / 0.25;
-(((1.0 - fall) * peak_h as f32) as i32)
} else if t < 0.65 {
let dip = (t - 0.5) / 0.15;
(dip * h as f32 * 0.15) as i32
} else {
0
};
let y = (base as i32 + y_offset).clamp(0, h as i32 - 1);
draw::dot(grid, x, y as usize);
}
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct CircuitTrace;
impl ProgressStyle for CircuitTrace {
fn name(&self) -> &str {
"circuit-trace"
}
fn theme(&self) -> &str {
"tech"
}
fn describe(&self) -> &str {
"Circuit board trace with routing turns, lit by 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 (cells_w, cells_h) = grid.dimensions();
let filled = ((ctx.eased * w as f32) as usize).min(w);
let seg_len = (w / 8).max(4);
let mut x = 0usize;
let mut y = h / 2;
let mut up = true;
while x < filled {
let run = seg_len + (hash(x as u32 * 3 + 17) % seg_len as u32) as usize;
let run_end = (x + run).min(filled);
draw::hline(grid, x, run_end.saturating_sub(1), y);
x = run_end;
if x >= filled {
break;
}
let jog = (h / 3).max(1);
let (y0, y1) = if up {
let new_y = y.saturating_sub(jog);
(new_y, y)
} else {
let new_y = (y + jog).min(h.saturating_sub(1));
(y, new_y)
};
draw::vline(grid, x.saturating_sub(1), y0, y1);
draw::dot(grid, x.saturating_sub(1), y0);
draw::dot(grid, x.saturating_sub(1), y1);
y = if up {
y.saturating_sub(jog)
} else {
(y + jog).min(h.saturating_sub(1))
};
up = !up;
}
let pulse_x = ((ctx.time * 0.7).fract() * filled as f32) as usize;
let pulse_x = pulse_x.min(filled.saturating_sub(1));
for dx in 0..3usize {
let px = pulse_x.saturating_sub(1) + dx;
if px < w {
draw::dot(grid, px, h / 2);
}
}
let filled_cells = (ctx.eased * cells_w as f32) as usize;
for cx in 0..filled_cells.min(cells_w) {
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}