use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, Color, DotmaxError};
use std::f32::consts::PI;
const TINT_START: Color = Color::rgb(96, 202, 255);
const TINT_END: Color = Color::rgb(52, 88, 222);
fn sample_tint(t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
let lerp = |a: u8, b: u8| (f32::from(a) + (f32::from(b) - f32::from(a)) * t) as u8;
Color::rgb(
lerp(TINT_START.r, TINT_END.r),
lerp(TINT_START.g, TINT_END.g),
lerp(TINT_START.b, TINT_END.b),
)
}
struct Tinted<S>(S);
impl<S: ProgressStyle> ProgressStyle for Tinted<S> {
fn name(&self) -> &str {
self.0.name()
}
fn theme(&self) -> &str {
self.0.theme()
}
fn describe(&self) -> &str {
self.0.describe()
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
self.0.render(grid, ctx)?;
grid.enable_color_support();
let (w, h) = grid.dimensions();
for y in 0..h {
for x in 0..w {
let ch = grid.get_char(x, y);
if ch != '\u{2800}' && ch != ' ' {
let t = (x as f32 / w.max(1) as f32 + ctx.time * 0.05).fract();
let tri = 1.0 - (2.0 * t - 1.0).abs();
let _ = grid.set_cell_color(x, y, sample_tint(tri));
}
}
}
Ok(())
}
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(Tinted(SmoothHbar)),
Box::new(Tinted(BlockyHbar)),
Box::new(Tinted(SegmentedLed)),
Box::new(Tinted(Equalizer)),
Box::new(Tinted(Thermometer)),
Box::new(Tinted(DitherRamp)),
Box::new(Tinted(BrickWall)),
Box::new(Tinted(BayerDither)),
Box::new(Tinted(StackedBars)),
Box::new(Tinted(Waterfall)),
Box::new(Tinted(DoubleEnded)),
Box::new(Tinted(GradientMeter)),
]
}
struct SmoothHbar;
impl ProgressStyle for SmoothHbar {
fn name(&self) -> &str {
"smooth-hbar"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Eighth-precise sub-cell smooth bar — the gold standard crisp fill using draw::hbar"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (_, cells_h) = grid.dimensions();
for cy in 0..cells_h {
draw::hbar(grid, cy, ctx.eased);
}
Ok(())
}
}
struct BlockyHbar;
impl ProgressStyle for BlockyHbar {
fn name(&self) -> &str {
"blocky-hbar"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Full-cell █ snap only — explicit coarse counterpart to smooth-hbar, no sub-cell precision"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let filled = (ctx.eased * cells_w as f32).floor() as usize;
let filled = filled.min(cells_w);
for cy in 0..cells_h {
for cx in 0..filled {
draw::glyph(grid, cx, cy, '█');
}
}
Ok(())
}
}
struct SegmentedLed;
impl ProgressStyle for SegmentedLed {
fn name(&self) -> &str {
"segmented-led"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Discrete lit █ segments separated by single-cell gaps, like a VU meter"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let seg_w = 2usize;
let gap_w = 1usize;
let stride = (seg_w + gap_w).max(1);
let n_segs = (cells_w / stride).max(1);
let lit = (ctx.eased * n_segs as f32).round() as usize;
let lit = lit.min(n_segs);
for s in 0..lit {
let x0 = s * stride;
for cx in x0..(x0 + seg_w).min(cells_w) {
for cy in 0..cells_h {
draw::glyph(grid, cx, cy, '█');
}
}
}
Ok(())
}
}
struct Equalizer;
impl ProgressStyle for Equalizer {
fn name(&self) -> &str {
"equalizer"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Animated spectrum equalizer: vblock columns with sinusoidal heights, lit count gated by eased"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let lit_cols = (ctx.eased * cells_w as f32).round() as usize;
let lit_cols = lit_cols.min(cells_w);
let cells_h_f = cells_h as f32;
for cx in 0..lit_cols {
let freq = 1.0 + (cx as f32 / cells_w.max(1) as f32) * 3.0;
let phase = ctx.time * 2.5 + cx as f32 * 0.4;
let raw = (phase * freq).sin() * 0.5 + 0.5; let col_height_f = raw * cells_h_f;
let full_cells = col_height_f.floor() as usize;
let frac_eighth = ((col_height_f.fract() * 8.0).round() as usize).min(8);
for row in 0..full_cells.min(cells_h) {
let cy = cells_h.saturating_sub(1).saturating_sub(row);
draw::vblock(grid, cx, cy, 8);
}
if full_cells < cells_h && frac_eighth > 0 {
let cy = cells_h.saturating_sub(1).saturating_sub(full_cells);
draw::vblock(grid, cx, cy, frac_eighth);
}
}
Ok(())
}
}
struct Thermometer;
impl ProgressStyle for Thermometer {
fn name(&self) -> &str {
"thermometer"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Vertical column filling bottom-to-top with vblock eighths, like a mercury thermometer"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
if cells_w == 0 || cells_h == 0 {
return Ok(());
}
let (dw, _dh) = draw::dot_dims(grid);
let dwi = dw as i32;
let col_r = cells_w / 2;
let col_l = col_r.saturating_sub(1);
let tube_rows = cells_h.saturating_sub(1).max(1);
let simmer = (ctx.time * PI * 0.5).sin() * 2.0;
let total_eighths = ((ctx.eased * tube_rows as f32 * 8.0) + simmer)
.clamp(0.0, tube_rows as f32 * 8.0)
.round() as usize;
let full_cells = (total_eighths / 8).min(tube_rows);
let rem_eighths = total_eighths % 8;
for row in 0..full_cells {
let cy = cells_h.saturating_sub(2).saturating_sub(row);
draw::vblock(grid, col_l, cy, 8);
draw::vblock(grid, col_r, cy, 8);
}
if full_cells < tube_rows && rem_eighths > 0 {
let cy = cells_h.saturating_sub(2).saturating_sub(full_cells);
draw::vblock(grid, col_l, cy, rem_eighths);
draw::vblock(grid, col_r, cy, rem_eighths);
}
let tube_x0 = col_l as i32 * 2; let tube_x1 = col_r as i32 * 2 + 1; let tube_bot = (cells_h as i32 - 1) * 4;
for y in 1..tube_bot {
draw::dot_i(grid, tube_x0 - 1, y);
draw::dot_i(grid, tube_x1 + 1, y);
}
draw::dot_i(grid, tube_x0 - 1, 0);
draw::dot_i(grid, tube_x1 + 1, 0);
let bx = (tube_x0 + tube_x1) as f32 / 2.0;
let by = tube_bot as f32 + 1.5;
for dy in tube_bot..tube_bot + 4 {
let fy = (dy as f32 - by) / 2.0;
let span = ((1.0 - fy * fy).max(0.0)).sqrt() * 4.5;
for dx in (bx - span).round() as i32..=(bx + span).round() as i32 {
draw::dot_i(grid, dx, dy);
}
}
let mut mark = 0i32;
let mut y = 1i32;
while y < tube_bot {
let len = if mark % 2 == 0 { 3 } else { 2 };
for j in 0..len {
draw::dot_i(grid, tube_x0 - 4 - j, y);
draw::dot_i(grid, tube_x1 + 4 + j, y);
}
mark += 1;
y += 3;
}
let level_y = (tube_bot - (total_eighths as i32 + 1) / 2).clamp(0, tube_bot);
let crawl = ((ctx.time * 4.0) as i32).rem_euclid(4);
for x in (0..dwi + 4).step_by(4) {
let x = x - crawl;
if x < tube_x0 - 8 || x > tube_x1 + 8 {
draw::dot_i(grid, x, level_y);
}
}
Ok(())
}
}
struct DitherRamp;
impl ProgressStyle for DitherRamp {
fn name(&self) -> &str {
"dither-ramp"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Left-to-right shade ramp ░▒▓█ whose density front advances with eased progress"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let frontier = ctx.eased * cells_w as f32;
for cy in 0..cells_h {
for cx in 0..cells_w {
let cell_f = cx as f32;
let behind = frontier - cell_f;
let level = if behind <= 0.0 {
0 } else if behind < 1.0 {
(behind * 4.0).ceil() as usize
} else {
let t = (cell_f / frontier.max(1.0)).clamp(0.0, 1.0);
(t * 4.0).round() as usize
};
draw::shade(grid, cx, cy, level.min(4));
}
}
Ok(())
}
}
struct BrickWall;
impl ProgressStyle for BrickWall {
fn name(&self) -> &str {
"brick-wall"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Running-bond brick masonry pattern (alternating offset rows of █) filling with eased"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let brick_w: usize = 3;
let frontier = (ctx.eased * cells_w as f32).round() as usize;
let frontier = frontier.min(cells_w);
for cy in 0..cells_h {
let offset = if cy % 2 == 1 { brick_w / 2 } else { 0 };
let mut cx = 0usize;
while cx < frontier {
let brick_start = if cx == 0 && offset > 0 {
0
} else {
cx
};
let body = (brick_w.saturating_sub(1)).max(1);
for bx in 0..body {
let target = brick_start + bx;
if target < frontier && target < cells_w {
let col_in_cycle = (target + cells_w - offset) % brick_w;
if col_in_cycle < brick_w.saturating_sub(1) {
draw::glyph(grid, target, cy, '█');
}
}
}
cx += brick_w;
}
}
Ok(())
}
}
struct BayerDither;
const BAYER4: [[u8; 4]; 4] = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];
impl ProgressStyle for BayerDither {
fn name(&self) -> &str {
"bayer-dither"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"4×4 Bayer ordered-dither reveal — threshold rises with eased, producing a stippled fill"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let threshold = (ctx.eased * 16.0).round() as u8;
for cy in 0..cells_h {
for cx in 0..cells_w {
let bx = cx % 4;
let by = cy % 4;
let bval = BAYER4[by][bx];
if bval < threshold {
draw::glyph(grid, cx, cy, '█');
} else if bval == threshold && threshold > 0 {
draw::shade(grid, cx, cy, 2); }
}
}
Ok(())
}
}
struct StackedBars;
impl ProgressStyle for StackedBars {
fn name(&self) -> &str {
"stacked-bars"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Multi-row stacked bar chart: each row a differently-shaded series filling with eased"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let n_series = cells_h.max(1);
for cy in 0..cells_h {
let series_offset = (cy as f32 / n_series as f32) * 0.2;
let series_frac = (ctx.eased - series_offset).clamp(0.0, 1.0);
let filled = (series_frac * cells_w as f32).round() as usize;
let filled = filled.min(cells_w);
let shade_level = 4 - (cy % 4);
for cx in 0..filled {
draw::shade(grid, cx, cy, shade_level);
}
}
Ok(())
}
}
struct Waterfall;
impl ProgressStyle for Waterfall {
fn name(&self) -> &str {
"waterfall"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Shade glyphs cascading downward (driven by time) with intensity from a horizontal wave"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
let frontier = ctx.eased * cells_w as f32;
for cy in 0..cells_h {
for cx in 0..cells_w {
if cx as f32 >= frontier {
continue;
}
let wave = ((cx as f32 * 0.5 - ctx.time * 3.0 + cy as f32 * 0.8) * PI * 0.5).sin();
let intensity = (wave * 0.5 + 0.5).clamp(0.0, 1.0);
let level = (intensity * 4.0).round() as usize;
draw::shade(grid, cx, cy, level.min(4));
}
}
Ok(())
}
}
struct DoubleEnded;
impl ProgressStyle for DoubleEnded {
fn name(&self) -> &str {
"double-ended"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Bar fills simultaneously from both ends toward the centre using smooth hbar logic"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
if cells_w == 0 || cells_h == 0 {
return Ok(());
}
let half = cells_w as f32 / 2.0;
let eighths_total = (ctx.eased * half * 8.0).round() as usize;
let full_cells = (eighths_total / 8).min(cells_w / 2);
let rem_eighths = eighths_total % 8;
for cy in 0..cells_h {
for cx in 0..full_cells.min(cells_w) {
draw::glyph(grid, cx, cy, '█');
}
if rem_eighths > 0 && full_cells < cells_w {
draw::glyph(grid, full_cells, cy, draw::H_BLOCKS[rem_eighths]);
}
let r_start = cells_w.saturating_sub(full_cells);
for cx in r_start..cells_w {
draw::glyph(grid, cx, cy, '█');
}
if rem_eighths > 0 && r_start > 0 {
let pcx = r_start.saturating_sub(1);
if pcx > full_cells {
draw::glyph(grid, pcx, cy, draw::H_BLOCKS[rem_eighths]);
}
}
}
Ok(())
}
}
struct GradientMeter;
impl ProgressStyle for GradientMeter {
fn name(&self) -> &str {
"gradient-meter"
}
fn theme(&self) -> &str {
"blocks"
}
fn describe(&self) -> &str {
"Smooth hbar fill combined with a ░ shade backdrop on the unfilled track"
}
fn render(&self, grid: &mut BrailleGrid, ctx: &BarContext) -> Result<(), DotmaxError> {
let (cells_w, cells_h) = grid.dimensions();
for cy in 0..cells_h {
for cx in 0..cells_w {
draw::shade(grid, cx, cy, 1); }
}
let eighths_total = (ctx.eased * cells_w as f32 * 8.0).round() as usize;
let full_cells = (eighths_total / 8).min(cells_w);
let rem_eighths = eighths_total % 8;
for cy in 0..cells_h {
for cx in 0..full_cells {
draw::glyph(grid, cx, cy, '█');
}
if rem_eighths > 0 && full_cells < cells_w {
draw::glyph(grid, full_cells, cy, draw::H_BLOCKS[rem_eighths]);
}
}
Ok(())
}
}