use super::super::draw;
use super::super::{BarContext, ProgressStyle};
use crate::{BrailleGrid, DotmaxError};
use std::f32::consts::PI;
fn line(grid: &mut BrailleGrid, x0: i32, y0: i32, x1: i32, y1: i32) {
let mut x = x0;
let mut y = y0;
let dx = (x1 - x0).abs();
let dy = -(y1 - y0).abs();
let sx: i32 = if x0 < x1 { 1 } else { -1 };
let sy: i32 = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
let max_steps = (dx + dy.abs() + 2) as usize;
let mut steps = 0usize;
loop {
draw::dot_i(grid, x, y);
if x == x1 && y == y1 {
break;
}
steps += 1;
if steps > max_steps {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x += sx;
}
if e2 <= dx {
err += dx;
y += sy;
}
}
}
#[inline]
fn hf(n: u32) -> f32 {
let mut x = n.wrapping_mul(2_654_435_761);
x ^= x >> 15;
x = x.wrapping_mul(2_246_822_519);
(x % 1000) as f32 / 1000.0
}
pub fn styles() -> Vec<Box<dyn ProgressStyle>> {
vec![
Box::new(VpTunnel),
Box::new(RoadHorizon),
Box::new(StarfieldDive),
Box::new(WireCorridor),
Box::new(CheckerFloor),
Box::new(InfiniteHallway),
Box::new(MineShaft),
Box::new(DepthBrackets),
Box::new(ApproachGate),
Box::new(PipeWormhole),
Box::new(ParallaxLayers),
]
}
struct VpTunnel;
impl ProgressStyle for VpTunnel {
fn name(&self) -> &str {
"vp-tunnel"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Concentric rectangles converge to a center vanishing point; rings fly \
toward the viewer — depth filled = eased, forward rush = time"
}
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 cx = (dw / 2) as f32;
let cy = (dh / 2) as f32;
let n_rings: usize = 8.max(dw.min(dh) / 2);
let scroll = (ctx.time * 0.4).fract();
let depth_rings = ((ctx.eased * n_rings as f32).ceil() as usize)
.min(n_rings)
.max(1);
for i in 0..depth_rings {
let raw = (i as f32 + scroll) / n_rings as f32;
let t = raw.fract();
let s = t * t;
let half_w = (cx * s).max(0.5) as i32;
let half_h = (cy * s).max(0.5) as i32;
if half_w <= 0 || half_h <= 0 {
continue;
}
let x0 = (cx as i32 - half_w).max(0);
let y0 = (cy as i32 - half_h).max(0);
let x1 = (cx as i32 + half_w).min(dw as i32 - 1);
let y1 = (cy as i32 + half_h).min(dh as i32 - 1);
line(grid, x0, y0, x1, y0);
line(grid, x0, y1, x1, y1);
line(grid, x0, y0, x0, y1);
line(grid, x1, y0, x1, y1);
line(grid, x0, y0, cx as i32, cy as i32);
line(grid, x1, y0, cx as i32, cy as i32);
line(grid, x0, y1, cx as i32, cy as i32);
line(grid, x1, y1, cx as i32, cy as i32);
}
draw::dot_i(grid, cx as i32, cy as i32);
Ok(())
}
}
struct RoadHorizon;
impl ProgressStyle for RoadHorizon {
fn name(&self) -> &str {
"road-horizon"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Two rails converge at a horizon point; dashed center line scrolls toward \
the viewer — how far you've driven = eased"
}
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 horizon_y = (dh / 3).max(1) as i32;
let vp_x = (dw / 2) as i32;
let road_half = (dw as i32 / 4).max(2);
let left_bot = (vp_x - road_half).max(0);
let right_bot = (vp_x + road_half).min(dw as i32 - 1);
line(grid, vp_x, horizon_y, left_bot, dh as i32 - 1);
line(grid, vp_x, horizon_y, right_bot, dh as i32 - 1);
draw::hline(grid, 0, dw - 1, horizon_y as usize);
let seg_count = 8usize;
let scroll_phase = (ctx.time * 0.5).fract();
for s in 0..seg_count {
let t_base = (s as f32 + scroll_phase) / seg_count as f32;
let t = t_base.fract();
let seg_y = (horizon_y as f32 + (dh as f32 - horizon_y as f32) * t) as i32;
let half_w = (road_half as f32 * t * 0.5) as i32;
if s % 2 == 0 {
draw::dot_i(grid, vp_x, seg_y);
if half_w > 1 {
draw::dot_i(grid, vp_x, seg_y - 1);
draw::dot_i(grid, vp_x, seg_y + 1);
}
}
}
let marker_t = ctx.eased;
let marker_y = (horizon_y as f32 + (dh as f32 - horizon_y as f32) * marker_t) as i32;
let marker_half = (road_half as f32 * marker_t) as i32;
let mx0 = (vp_x - marker_half).max(0);
let mx1 = (vp_x + marker_half).min(dw as i32 - 1);
if marker_y >= 0 && marker_y < dh as i32 {
line(grid, mx0, marker_y, mx1, marker_y);
}
Ok(())
}
}
struct StarfieldDive;
impl ProgressStyle for StarfieldDive {
fn name(&self) -> &str {
"starfield-dive"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Stars stream from the center vanishing point outward; streak length = \
warp speed from eased; active-star count grows with progress"
}
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 cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let max_r = cx.min(cy * 1.5).max(1.0);
let num_stars: u32 = 60;
let active = ((ctx.eased * num_stars as f32).ceil() as u32).clamp(1, num_stars);
let streak_len = (ctx.eased * 12.0 + 1.0) as i32;
let speed = 0.6 + ctx.eased * 1.8;
for i in 0..active {
let angle = hf(i) * 2.0 * PI;
let phase = hf(i + 100);
let r_frac = ((phase + ctx.time * speed * 0.07).fract()).clamp(0.0, 1.0);
let r = r_frac * r_frac * max_r;
let px = cx + angle.cos() * r;
let py = cy + angle.sin() * r * 0.55;
for s in 0..streak_len {
let sr = (r - s as f32 * 0.8 * r_frac).max(0.0);
let sx = cx + angle.cos() * sr;
let sy = cy + angle.sin() * sr * 0.55;
draw::dot_i(grid, sx as i32, sy as i32);
}
draw::dot_i(grid, px as i32, py as i32);
}
Ok(())
}
}
struct WireCorridor;
impl ProgressStyle for WireCorridor {
fn name(&self) -> &str {
"wire-corridor"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Wireframe corridor: floor, ceiling, and wall grid lines converge to a \
central vanishing point; floor lines scroll forward with time"
}
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 vpx = (dw / 2) as i32;
let vpy = (dh / 2) as i32;
draw::hline(grid, 0, dw - 1, vpy as usize);
line(grid, 0, 0, vpx, vpy);
line(grid, dw as i32 - 1, 0, vpx, vpy);
line(grid, 0, dh as i32 - 1, vpx, vpy);
line(grid, dw as i32 - 1, dh as i32 - 1, vpx, vpy);
let stripe_count = 6usize;
let lit_stripes = ((ctx.eased * stripe_count as f32).ceil() as usize)
.min(stripe_count)
.max(1);
for s in 0..lit_stripes {
let x = (s + 1) as i32 * dw as i32 / (stripe_count + 1) as i32;
line(grid, x, 0, vpx, vpy);
line(grid, x, dh as i32 - 1, vpx, vpy);
}
let floor_count = 8usize;
let scroll = (ctx.time * 0.45).fract();
for f in 0..floor_count {
let t = ((f as f32 + scroll) / floor_count as f32).fract();
let fy = (vpy as f32 + (dh as f32 - vpy as f32) * t * t) as i32;
if fy < 0 || fy >= dh as i32 {
continue;
}
let fx_half = (dw as f32 / 2.0 * t).max(0.5) as i32;
let fx0 = (vpx - fx_half).max(0);
let fx1 = (vpx + fx_half).min(dw as i32 - 1);
line(grid, fx0, fy, fx1, fy);
}
for f in 0..floor_count {
let t = ((f as f32 + scroll) / floor_count as f32).fract();
let fy = (vpy as f32 - (vpy as f32) * t * t) as i32;
if fy < 0 || fy >= dh as i32 {
continue;
}
let fx_half = (dw as f32 / 2.0 * t).max(0.5) as i32;
let fx0 = (vpx - fx_half).max(0);
let fx1 = (vpx + fx_half).min(dw as i32 - 1);
line(grid, fx0, fy, fx1, fy);
}
draw::dot_i(grid, vpx, vpy);
Ok(())
}
}
struct CheckerFloor;
impl ProgressStyle for CheckerFloor {
fn name(&self) -> &str {
"checker-floor"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Perspective checkerboard floor: rows compress toward the horizon; \
pattern scrolls toward the viewer with time; depth = eased"
}
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 horizon_y = (dh / 3).max(1);
draw::hline(grid, 0, dw - 1, horizon_y);
let vpx = dw / 2;
let floor_h = dh - horizon_y;
let max_rows = 10usize;
let visible_rows = ((ctx.eased * max_rows as f32).ceil() as usize)
.min(max_rows)
.max(1);
let scroll = (ctx.time * 0.35).fract();
for row in 0..visible_rows {
let t_raw = (row as f32 + scroll) / max_rows as f32;
let t = t_raw.fract();
let y = (horizon_y as f32 + floor_h as f32 * (1.0 - t * t)) as usize;
if y >= dh {
continue;
}
let half_w = (vpx as f32 * (1.0 - t * 0.9)).max(0.0) as usize;
let x0 = vpx.saturating_sub(half_w);
let x1 = (vpx + half_w).min(dw - 1);
let col_count = (half_w * 2 / 4).max(1);
for col in 0..col_count {
let cx0 = x0 + col * (x1 - x0 + 1) / col_count;
let cx1 = x0 + (col + 1) * (x1 - x0 + 1) / col_count;
let checker = (row + col) % 2;
if checker == 0 {
draw::hline(grid, cx0, cx1.min(dw - 1), y);
}
}
}
line(grid, 0, dh as i32 - 1, vpx as i32, horizon_y as i32);
line(
grid,
dw as i32 - 1,
dh as i32 - 1,
vpx as i32,
horizon_y as i32,
);
Ok(())
}
}
struct InfiniteHallway;
impl ProgressStyle for InfiniteHallway {
fn name(&self) -> &str {
"infinite-hallway"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Nested doorframe rectangles at increasing depth — each door crossed = \
one step of eased; doors scroll toward viewer with time"
}
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 cx = (dw / 2) as i32;
let cy = (dh / 2) as i32;
let n_doors = 8usize;
let scroll = (ctx.time * 0.4).fract();
let visible = ((ctx.eased * n_doors as f32).ceil() as usize)
.min(n_doors)
.max(1);
for i in 0..visible {
let raw = (i as f32 + scroll) / n_doors as f32;
let t = raw.fract();
let s = t * t;
let hw = ((dw as f32 / 2.0) * s).max(1.0) as i32;
let hh = ((dh as f32 / 2.0) * s).max(1.0) as i32;
let x0 = (cx - hw).max(0);
let y0 = (cy - hh).max(0);
let x1 = (cx + hw).min(dw as i32 - 1);
let y1 = (cy + hh).min(dh as i32 - 1);
line(grid, x0, y0, x1, y0); line(grid, x0, y0, x0, y1); line(grid, x1, y0, x1, y1);
line(grid, x0, y0, cx, cy);
line(grid, x1, y0, cx, cy);
}
draw::dot_i(grid, cx, cy);
draw::dot_i(grid, cx - 1, cy);
draw::dot_i(grid, cx + 1, cy);
draw::dot_i(grid, cx, cy - 1);
draw::dot_i(grid, cx, cy + 1);
Ok(())
}
}
struct MineShaft;
impl ProgressStyle for MineShaft {
fn name(&self) -> &str {
"mine-shaft"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Descending shaft: floor markers scroll upward with time; depth gauge \
on the right fills with eased; you're falling down and in"
}
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 shaft_top_half = (dw as i32 / 3).max(2);
let shaft_bot_half = (dw as i32 / 6).max(1);
let vpx = (dw / 2) as i32;
line(
grid,
vpx - shaft_top_half,
0,
vpx - shaft_bot_half,
dh as i32 - 1,
);
line(
grid,
vpx + shaft_top_half,
0,
vpx + shaft_bot_half,
dh as i32 - 1,
);
let marker_count = 10usize;
let scroll = (ctx.time * 0.55).fract();
for m in 0..marker_count {
let t = ((m as f32 + scroll) / marker_count as f32).fract();
let y = (dh as f32 * (1.0 - t)) as i32;
if y < 0 || y >= dh as i32 {
continue;
}
let frac_y = y as f32 / dh as f32;
let half_w =
(shaft_top_half as f32 * (1.0 - frac_y) + shaft_bot_half as f32 * frac_y) as i32;
let x0 = (vpx - half_w).max(0);
let x1 = (vpx + half_w).min(dw as i32 - 1);
line(grid, x0, y, x1, y);
draw::dot_i(grid, x0 - 1, y);
draw::dot_i(grid, x1 + 1, y);
}
let gauge_x = (dw as i32 - 2).max(0);
let filled_h = (ctx.eased * dh as f32).round() as i32;
for y in 0..filled_h.min(dh as i32) {
draw::dot_i(grid, gauge_x, y);
draw::dot_i(grid, gauge_x + 1, y);
}
draw::vline(grid, gauge_x as usize, 0, dh - 1);
draw::dot_i(grid, gauge_x, 0);
draw::dot_i(grid, gauge_x, dh as i32 - 1);
Ok(())
}
}
struct DepthBrackets;
impl ProgressStyle for DepthBrackets {
fn name(&self) -> &str {
"depth-brackets"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Nested [ [ [ > ] ] ] bracket pairs zoom inward; visible depth = \
eased; inner marker pulses with time — pure typographic perspective"
}
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 cx = (dw / 2) as i32;
let cy = (dh / 2) as i32;
let max_depth = 6usize;
let depth = ((ctx.eased * max_depth as f32).ceil() as usize)
.min(max_depth)
.max(1);
let breath = (ctx.time * 1.5).sin() * 0.04 + 1.0;
for d in 0..depth {
let frac = (max_depth - d) as f32 / max_depth as f32;
let hw = ((dw as f32 / 2.0) * frac * breath).max(1.0) as i32;
let hh = ((dh as f32 / 2.0) * frac * breath).max(1.0) as i32;
let x0 = (cx - hw).max(0);
let y0 = (cy - hh).max(0);
let x1 = (cx + hw).min(dw as i32 - 1);
let y1 = (cy + hh).min(dh as i32 - 1);
draw::dot_i(grid, x0, y0);
draw::dot_i(grid, x0, y1);
draw::vline(grid, x0 as usize, y0 as usize, y1 as usize);
draw::dot_i(grid, x0 + 1, y0);
draw::dot_i(grid, x0 + 1, y1);
draw::dot_i(grid, x1, y0);
draw::dot_i(grid, x1, y1);
draw::vline(grid, x1 as usize, y0 as usize, y1 as usize);
draw::dot_i(grid, x1 - 1, y0);
draw::dot_i(grid, x1 - 1, y1);
}
let pulse = ((ctx.time * 3.0).sin() * 1.5) as i32;
draw::dot_i(grid, cx + pulse, cy);
draw::dot_i(grid, cx + pulse - 1, cy - 1);
draw::dot_i(grid, cx + pulse - 1, cy + 1);
draw::dot_i(grid, cx + pulse - 2, cy - 2);
draw::dot_i(grid, cx + pulse - 2, cy + 2);
Ok(())
}
}
struct ApproachGate;
impl ProgressStyle for ApproachGate {
fn name(&self) -> &str {
"approach-gate"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"A portal ring grows from the vanishing point until it fills the screen \
at 100%; multiple rings stream toward the viewer continuously"
}
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 cx = (dw / 2) as f32;
let cy = (dh / 2) as f32;
let max_rx = cx;
let max_ry = cy;
let n_rings = 5usize;
let scroll = (ctx.time * 0.5).fract();
for i in 0..n_rings {
let raw = (i as f32 + scroll) / n_rings as f32;
let phase = raw.fract();
let t = if i == 0 {
phase.max(ctx.eased * phase)
} else {
phase * ctx.eased
};
let rx = (max_rx * t * t).max(0.5);
let ry = (max_ry * t * t).max(0.5);
let steps = ((rx + ry) * 4.0).max(8.0) as usize;
let mut prev: Option<(i32, i32)> = None;
for s in 0..=steps {
let angle = s as f32 / steps as f32 * 2.0 * PI;
let px = (cx + rx * angle.cos()) as i32;
let py = (cy + ry * angle.sin()) as i32;
if let Some((ppx, ppy)) = prev {
line(grid, ppx, ppy, px, py);
}
prev = Some((px, py));
}
}
draw::dot_i(grid, cx as i32, cy as i32);
Ok(())
}
}
struct PipeWormhole;
impl ProgressStyle for PipeWormhole {
fn name(&self) -> &str {
"pipe-wormhole"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Circular pipe tunnel with radial spokes; spokes rotate with time as \
you travel through — rings fill in with eased for depth"
}
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 cx = dw as f32 / 2.0;
let cy = dh as f32 / 2.0;
let max_r = cx.min(cy).max(1.0);
let n_rings = 6usize;
let visible = ((ctx.eased * n_rings as f32).ceil() as usize)
.min(n_rings)
.max(1);
for r_idx in 0..visible {
let t = (r_idx + 1) as f32 / n_rings as f32;
let r = max_r * t;
let steps = ((r * 2.0 * PI).max(8.0)) as usize;
let mut prev: Option<(i32, i32)> = None;
for s in 0..=steps {
let angle = s as f32 / steps as f32 * 2.0 * PI;
let px = (cx + r * angle.cos()) as i32;
let py = (cy + r * angle.sin() * 0.55) as i32;
if let Some((ppx, ppy)) = prev {
line(grid, ppx, ppy, px, py);
}
prev = Some((px, py));
}
}
let spoke_count = 8usize;
let rot = ctx.time * 0.8;
let outer_r = max_r * (visible as f32 / n_rings as f32);
for s in 0..spoke_count {
let angle = rot + s as f32 * 2.0 * PI / spoke_count as f32;
let x1 = (cx + outer_r * angle.cos()) as i32;
let y1 = (cy + outer_r * angle.sin() * 0.55) as i32;
line(grid, cx as i32, cy as i32, x1, y1);
}
draw::dot_i(grid, cx as i32, cy as i32);
Ok(())
}
}
struct ParallaxLayers;
impl ProgressStyle for ParallaxLayers {
fn name(&self) -> &str {
"parallax-layers"
}
fn theme(&self) -> &str {
"perspective"
}
fn describe(&self) -> &str {
"Three line-layers scroll at 3× speed ratios — background, midground, \
foreground — giving pure parallax depth; layer count = eased"
}
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 n_layers = 3usize;
let active = ((ctx.eased * n_layers as f32).ceil() as usize)
.min(n_layers)
.max(1);
let layers: [(f32, f32, usize, usize); 3] = [
(0.25, 0.3, 8, 3), (0.55, 0.9, 6, 4), (0.85, 2.2, 4, 3), ];
for (idx, &(y_frac, speed, period, on)) in layers.iter().enumerate() {
if idx >= active {
break;
}
let y = (dh as f32 * y_frac) as usize;
if y >= dh {
continue;
}
let offset = ((ctx.time * speed * dw as f32 * 0.1) as usize) % period.max(1);
for x in 0..dw {
let phase = (x + offset) % period.max(1);
if phase < on {
draw::dot(grid, x, y);
if idx == 2 && y + 1 < dh {
draw::dot(grid, x, y + 1);
}
}
}
let pole_h = match idx {
0 => 1usize,
1 => 2,
_ => 3,
};
let pole_spacing = dw / (idx * 3 + 4).max(1);
let pole_offset = ((ctx.time * speed * 0.5) as usize * pole_spacing / dw.max(1))
% pole_spacing.max(1);
let mut px = pole_offset;
while px < dw {
for py in 0..pole_h {
let dot_y = y + py + 1;
if dot_y < dh {
draw::dot(grid, px, dot_y);
}
if y >= py + 1 {
draw::dot(grid, px, y - py - 1);
}
}
px += pole_spacing.max(1);
}
}
Ok(())
}
}