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(PhoneBattery),
Box::new(WifiConnect),
Box::new(BluetoothPair),
Box::new(DiskDefrag),
Box::new(CrtPowerOn),
Box::new(DialUpModem),
Box::new(VinylSpinUp),
Box::new(DroneProps),
Box::new(ActivityRing),
Box::new(UsbTransfer),
Box::new(GearTrain),
Box::new(EinkRefresh),
]
}
struct PhoneBattery;
impl ProgressStyle for PhoneBattery {
fn name(&self) -> &str {
"phone-battery"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Smartphone battery outline fills with charge; lightning bolt pulses"
}
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 nub_w = (w / 16).max(1);
let body_w = w.saturating_sub(nub_w + 2);
let body_h = h.saturating_sub(2).max(1);
let bx = 0usize;
let by = 1usize;
draw::rect_outline(grid, bx, by, body_w.max(2), body_h.max(2));
let nub_y0 = by + body_h / 4;
let nub_h = body_h / 2;
draw::fill_rect(grid, bx + body_w, nub_y0, nub_w, nub_h.max(1));
let inner_w = body_w.saturating_sub(4).max(1);
let inner_h = body_h.saturating_sub(4).max(1);
let filled_w = ((ctx.eased * inner_w as f32) as usize).min(inner_w);
if filled_w > 0 {
draw::fill_rect(grid, bx + 2, by + 2, filled_w, inner_h);
}
let bolt_x = (bx + body_w / 2) as i32;
let bolt_mid = (by + body_h / 2) as i32;
let bolt_h = inner_h as i32;
let blink = (ctx.time * 2.0).fract() > 0.3 || ctx.eased > 0.5;
if blink {
for dy in 0..bolt_h / 2 {
let y = bolt_mid - dy as i32;
let x = bolt_x + (dy as f32 * 0.6) as i32;
draw::dot_i(grid, x, y);
draw::dot_i(grid, x + 1, y);
}
for dy in 0..=bolt_h / 2 {
let y = bolt_mid + dy as i32;
let x = bolt_x - (dy as f32 * 0.6) as i32;
draw::dot_i(grid, x, y);
draw::dot_i(grid, x + 1, y);
}
}
let n_bubbles = 4usize;
for b in 0..n_bubbles {
let phase = b as f32 / n_bubbles as f32;
let t = (ctx.time * 0.5 + phase).fract();
let bub_x = bx + 2 + (hashf(b as u32 * 7 + 3) * inner_w as f32) as usize;
let bub_x = bub_x.min(bx + body_w.saturating_sub(3));
let bub_y = by
+ 2
+ inner_h
.saturating_sub(1)
.saturating_sub((t * inner_h as f32) as usize);
let fill_right = bx + 2 + filled_w;
if bub_x < fill_right {
draw::dot(grid, bub_x, bub_y);
if bub_x + 1 < fill_right {
draw::dot(grid, bub_x + 1, bub_y);
}
}
}
let filled_cells = ((ctx.eased * cells_w as f32) as usize).min(cells_w);
for cx in 0..filled_cells {
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 WifiConnect;
impl ProgressStyle for WifiConnect {
fn name(&self) -> &str {
"wifi-connect"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"WiFi arcs pulse outward while connecting, then lock solid at 100%"
}
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 cx_dot = w / 2;
let cy_dot = h.saturating_sub(1);
let n_arcs = 4usize;
let arcs_lit = (ctx.eased * n_arcs as f32).floor() as usize;
let connected = arcs_lit >= n_arcs;
draw::dot(grid, cx_dot, cy_dot);
if cx_dot + 1 < w {
draw::dot(grid, cx_dot + 1, cy_dot);
}
for arc in 0..n_arcs {
let radius = (arc + 1) as f32 * (h as f32 / (n_arcs + 1) as f32);
let lit = arc < arcs_lit;
let pulse_phase = (ctx.time * 1.5 - arc as f32 * 0.4).fract();
let pulse = (pulse_phase * PI * 2.0).sin() * 0.5 + 0.5;
let draw_it = lit || (!connected && pulse > 0.5);
if draw_it {
let steps = ((PI * radius) as usize).max(8);
for step in 0..=steps {
let angle = PI * (step as f32 / steps as f32);
let dx = (angle.cos() * radius) as i32;
let dy = -(angle.sin() * radius * 0.6) as i32; draw::dot_i(grid, cx_dot as i32 + dx, cy_dot as i32 + dy);
if !lit {
}
}
if lit {
let t = arc as f32 / n_arcs.max(1) as f32;
let color = ctx.palette.sample(t);
let cell_cx = cx_dot / 2;
let r_cells = (radius as usize / 2).max(1);
let cx0 = cell_cx.saturating_sub(r_cells);
let cx1 = (cell_cx + r_cells).min(cells_w.saturating_sub(1));
let cy_cell = cy_dot / 4;
let r_cell_h = (radius as usize / 4).max(1);
let cy0 = cy_cell.saturating_sub(r_cell_h);
for cy in cy0..cells_h {
draw::tint_row(grid, cy, cx0, cx1, color);
}
}
}
}
if connected {
let blink_off = (ctx.time * 3.0).fract() < 0.2;
if !blink_off {
draw::hline(
grid,
cx_dot.saturating_sub(2),
(cx_dot + 2).min(w - 1),
cy_dot,
);
let full_color = ctx.palette.sample(1.0);
draw::tint_row(
grid,
cells_h.saturating_sub(1),
(cells_w / 2).saturating_sub(1),
(cells_w / 2 + 1).min(cells_w - 1),
full_color,
);
}
}
Ok(())
}
}
struct BluetoothPair;
impl ProgressStyle for BluetoothPair {
fn name(&self) -> &str {
"bluetooth-pair"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Bluetooth rune glyph at center; handshake blips pulse outward while pairing"
}
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 cx = w / 2;
let cy = h / 2;
let spine_h = (h * 3 / 4).max(2);
let y0 = cy.saturating_sub(spine_h / 2);
let y1 = (cy + spine_h / 2).min(h.saturating_sub(1));
draw::vline(grid, cx, y0, y1);
let arm = (h / 5).max(1);
for i in 0..arm {
draw::dot_i(grid, cx as i32 + i as i32, (y0 + arm) as i32 - i as i32);
}
for i in 0..arm {
draw::dot_i(grid, cx as i32 + i as i32, (y0 + arm) as i32 + i as i32);
}
for i in 0..arm {
draw::dot_i(grid, cx as i32 + i as i32, cy as i32 + i as i32);
}
for i in 0..arm {
draw::dot_i(grid, cx as i32 + i as i32, (cy + arm) as i32 - i as i32);
}
let n_blips = 5usize;
let paired = ctx.eased >= 1.0;
for b in 0..n_blips {
let phase = b as f32 / n_blips as f32;
let t = if paired {
1.0f32
} else {
(ctx.time * 0.8 + phase).fract()
};
let reach = (t * (w / 2) as f32) as usize;
let lx = cx.saturating_sub(reach);
draw::dot(grid, lx, cy);
let rx = (cx + reach).min(w.saturating_sub(1));
draw::dot(grid, rx, cy);
}
let filled_cells = ((ctx.eased * cells_w as f32) as usize).min(cells_w);
let mid_cell = cells_w / 2;
let reach_cells = (filled_cells / 2).max(1);
let cx0 = mid_cell.saturating_sub(reach_cells);
let cx1 = (mid_cell + reach_cells).min(cells_w.saturating_sub(1));
for cx_c in cx0..=cx1 {
let t = (cx_c.saturating_sub(cx0)) as f32 / (cx1.saturating_sub(cx0) + 1) as f32;
let color = ctx.palette.sample(t);
for cy_c in 0..cells_h {
draw::tint_row(grid, cy_c, cx_c, cx_c, color);
}
}
Ok(())
}
}
struct DiskDefrag;
impl ProgressStyle for DiskDefrag {
fn name(&self) -> &str {
"disk-defrag"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Disk defrag: chaotic shade blocks consolidate into solid filled region"
}
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 total_cells = cells_w * cells_h;
let defragged = (ctx.eased * total_cells as f32) as usize;
for cy in 0..cells_h {
for cx in 0..cells_w {
let linear = cy * cells_w + cx;
let color = ctx
.palette
.sample(linear as f32 / total_cells.max(1) as f32);
if linear < defragged {
draw::glyph(grid, cx, cy, '█');
draw::tint_row(grid, cy, cx, cx, color);
} else {
let epoch = (ctx.time * 4.0) as u32;
let h_val = hash(linear as u32 * 31 + epoch * 7 + 3);
let shade_level = (h_val % 4) as usize; if shade_level > 0 {
draw::shade(grid, cx, cy, shade_level);
let frag_color = ctx.palette.sample(0.15);
draw::tint_row(grid, cy, cx, cx, frag_color);
}
}
}
}
Ok(())
}
}
struct CrtPowerOn;
impl ProgressStyle for CrtPowerOn {
fn name(&self) -> &str {
"crt-power-on"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"CRT power-on: bright center line expands vertically to fill the screen"
}
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 mid = h / 2;
let beam_h = ((ctx.eased * h as f32) as usize).max(1).min(h);
let y0 = mid.saturating_sub(beam_h / 2);
let y1 = (y0 + beam_h).min(h);
for y in y0..y1 {
draw::hline(grid, 0, w.saturating_sub(1), y);
}
if y0 > 0 {
draw::hline(grid, 0, w.saturating_sub(1), y0.saturating_sub(1));
}
if y1 < h {
draw::hline(grid, 0, w.saturating_sub(1), y1);
}
if beam_h > 4 {
let noise_lines = beam_h / 4;
for nl in 0..noise_lines {
let y_nl = y0 + 2 + nl * 4;
if y_nl >= y1 {
break;
}
for x in 0..w {
let on = (hash((x as u32).wrapping_add(y_nl as u32 * 17)) % 5) == 0;
if on {
draw::dot(grid, x, y_nl);
}
}
}
}
let cy_center = cells_h / 2;
let beam_cells = (beam_h / 4).max(1);
let cy0 = cy_center.saturating_sub(beam_cells / 2);
let cy1 = (cy0 + beam_cells).min(cells_h.saturating_sub(1));
for cy in cy0..=cy1 {
let t = (cy.saturating_sub(cy0)) as f32 / (cy1.saturating_sub(cy0) + 1).max(1) as f32;
let color = ctx.palette.sample(t);
draw::tint_row(grid, cy, 0, cells_w.saturating_sub(1), color);
}
Ok(())
}
}
struct DialUpModem;
impl ProgressStyle for DialUpModem {
fn name(&self) -> &str {
"dialup-modem"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Dial-up modem: chaotic noise waveform settles to a clean carrier tone 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 (cells_w, cells_h) = grid.dimensions();
let mid = (h / 2) as i32;
let amp = (h / 2).saturating_sub(1) as f32;
for x in 0..w {
let carrier_phase = x as f32 / w as f32 * 4.0 * PI + ctx.time * 3.0;
let clean_y = mid + (carrier_phase.sin() * amp * 0.8) as i32;
let epoch = (ctx.time * 8.0) as u32;
let noise = hashf(x as u32 * 13 + epoch * 37) * 2.0 - 1.0;
let noisy_y = mid + ((carrier_phase.sin() * 0.8 + noise * 1.5) * amp) as i32;
let y = (noisy_y as f32 * (1.0 - ctx.eased) + clean_y as f32 * ctx.eased) as i32;
draw::dot_i(grid, x as i32, y);
draw::dot_i(grid, x as i32, y + 1);
}
let filled_cells = ((ctx.eased * cells_w as f32) as usize).min(cells_w);
for cx in 0..cells_w {
let t = cx as f32 / cells_w.max(1) as f32;
let is_settled = cx < filled_cells;
let color = if is_settled {
ctx.palette.sample(t)
} else {
ctx.palette.sample(0.05)
};
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx, cx, color);
}
}
Ok(())
}
}
struct VinylSpinUp;
impl ProgressStyle for VinylSpinUp {
fn name(&self) -> &str {
"vinyl-spin"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Vinyl/CD disc outline spins up; rotation speed 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 cx = w / 2;
let cy = h / 2;
let r_max = (w.min(h * 2) / 2).saturating_sub(1).max(1);
let omega = ctx.eased * 2.0 * PI * 2.0; let angle = ctx.time * omega;
let steps = (2.0 * PI * r_max as f32) as usize + 4;
for i in 0..steps {
let theta = i as f32 / steps as f32 * 2.0 * PI;
let dx = (theta.cos() * r_max as f32) as i32;
let dy = (theta.sin() * r_max as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
let r_inner = (r_max * 3 / 10).max(1);
let inner_steps = (2.0 * PI * r_inner as f32) as usize + 4;
for i in 0..inner_steps {
let theta = i as f32 / inner_steps as f32 * 2.0 * PI;
let dx = (theta.cos() * r_inner as f32) as i32;
let dy = (theta.sin() * r_inner as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
draw::dot(grid, cx, cy);
let mark_steps = (r_max - r_inner).max(1);
for step in 0..=mark_steps {
let r = r_inner + step;
let dx = (angle.cos() * r as f32) as i32;
let dy = (angle.sin() * r as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
let n_grooves = 3usize;
for g in 1..=n_grooves {
let r_g = r_inner + (r_max - r_inner) * g / (n_grooves + 1);
if r_g == 0 {
continue;
}
let g_steps = (2.0 * PI * r_g as f32) as usize + 4;
for i in (0..g_steps).step_by(3) {
let theta = i as f32 / g_steps as f32 * 2.0 * PI;
let dx = (theta.cos() * r_g as f32) as i32;
let dy = (theta.sin() * r_g as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
}
for cx_c in 0..cells_w {
let t = cx_c as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(ctx.eased * t);
for cy_c in 0..cells_h {
draw::tint_row(grid, cy_c, cx_c, cx_c, color);
}
}
Ok(())
}
}
struct DroneProps;
impl ProgressStyle for DroneProps {
fn name(&self) -> &str {
"drone-props"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Quad-drone: four propeller arcs spin up 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 (cells_w, cells_h) = grid.dimensions();
let qw = w / 2;
let qh = h / 2;
let r = (qw.min(qh * 2) / 2).max(1);
let omega = ctx.eased * 2.0 * PI * 3.0;
let angle = ctx.time * omega;
let hubs = [
(qw / 2, qh / 2),
(w - qw / 2, qh / 2),
(qw / 2, h - qh / 2),
(w - qw / 2, h - qh / 2),
];
let body_cx = w / 2;
let body_cy = h / 2;
draw::dot(grid, body_cx, body_cy);
for (hx, hy) in &hubs {
let dx = *hx as i32 - body_cx as i32;
let dy = *hy as i32 - body_cy as i32;
let steps = dx.abs().max(dy.abs()).max(1);
for step in 0..steps {
let t = step as f32 / steps as f32;
let px = body_cx as i32 + (dx as f32 * t) as i32;
let py = body_cy as i32 + (dy as f32 * t) as i32;
draw::dot_i(grid, px, py);
}
}
for (prop_idx, (hx, hy)) in hubs.iter().enumerate() {
let hub_angle = angle + prop_idx as f32 * PI * 0.5; for blade in 0..2usize {
let blade_angle = hub_angle + blade as f32 * PI;
let arc_sweep = PI / 3.0;
let arc_steps = (arc_sweep * r as f32) as usize + 4;
for step in 0..arc_steps {
let theta = blade_angle - arc_sweep / 2.0
+ step as f32 / arc_steps.max(1) as f32 * arc_sweep;
let dx = (theta.cos() * r as f32) as i32;
let dy = (theta.sin() * r as f32 * 0.5) as i32;
draw::dot_i(grid, *hx as i32 + dx, *hy as i32 + dy);
}
}
draw::dot(grid, *hx, *hy);
}
for cx_c in 0..cells_w {
for cy_c in 0..cells_h {
let t = cx_c as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t * ctx.eased);
draw::tint_row(grid, cy_c, cx_c, cx_c, color);
}
}
Ok(())
}
}
struct ActivityRing;
impl ProgressStyle for ActivityRing {
fn name(&self) -> &str {
"activity-ring"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Smartwatch activity ring closes clockwise as progress fills it"
}
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 cx = w / 2;
let cy = h / 2;
let r_outer = (w.min(h * 2) / 2).saturating_sub(1).max(2);
let r_inner = r_outer.saturating_sub((r_outer / 4).max(1));
let start_angle = -PI / 2.0;
let sweep = ctx.eased * 2.0 * PI;
let steps = ((2.0 * PI * r_outer as f32) as usize + 4).max(16);
for i in 0..=steps {
let frac = i as f32 / steps as f32;
let theta = frac * 2.0 * PI;
let rel = (theta - (start_angle + PI * 2.0)).rem_euclid(2.0 * PI);
if rel > sweep {
continue;
}
for r in r_inner..=r_outer {
let dx = (theta.cos() * r as f32) as i32;
let dy = (theta.sin() * r as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
}
for i in 0..=steps {
let frac = i as f32 / steps as f32;
let theta = frac * 2.0 * PI;
let rel = (theta - (start_angle + PI * 2.0)).rem_euclid(2.0 * PI);
if rel <= sweep {
continue;
}
if i % 3 != 0 {
continue;
} let r = (r_inner + r_outer) / 2;
let dx = (theta.cos() * r as f32) as i32;
let dy = (theta.sin() * r as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
}
let tip_theta = start_angle + sweep;
for r in r_inner..=r_outer {
let dx = (tip_theta.cos() * r as f32) as i32;
let dy = (tip_theta.sin() * r as f32 * 0.5) as i32;
draw::dot_i(grid, cx as i32 + dx, cy as i32 + dy);
draw::dot_i(grid, cx as i32 + dx + 1, cy as i32 + dy);
}
let mid_c = cells_w / 2;
let r_cells = (r_outer / 2).max(1);
let cx0 = mid_c.saturating_sub(r_cells);
let cx1 = (mid_c + r_cells).min(cells_w.saturating_sub(1));
for cx_c in cx0..=cx1 {
let t = (cx_c.saturating_sub(cx0)) as f32 / (cx1.saturating_sub(cx0) + 1).max(1) as f32;
let color = ctx.palette.sample(ctx.eased * t + ctx.eased * 0.2);
for cy_c in 0..cells_h {
draw::tint_row(grid, cy_c, cx_c, cx_c, color);
}
}
Ok(())
}
}
struct UsbTransfer;
impl ProgressStyle for UsbTransfer {
fn name(&self) -> &str {
"usb-transfer"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"USB transfer: file packets fly left→right; count and speed scale 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 mid = h / 2;
let dev_w = (w / 16).max(2);
draw::fill_rect(grid, 0, 0, dev_w, h);
draw::fill_rect(grid, w.saturating_sub(dev_w), 0, dev_w, h);
draw::hline(grid, dev_w, w.saturating_sub(dev_w + 1), mid);
let max_packets = 6usize;
let active = ((ctx.eased * max_packets as f32).ceil() as usize).min(max_packets);
let track_w = w.saturating_sub(dev_w * 2 + 2);
for p in 0..active {
let phase = p as f32 / max_packets as f32;
let speed = 0.4 + ctx.eased * 0.6;
let t = (ctx.time * speed + phase).fract();
let x0 = dev_w + 1 + (t * track_w as f32) as usize;
let x0 = x0.min(w.saturating_sub(dev_w + 3));
let pkt_w = (w / 20).max(2).min(4);
let pkt_h = 2usize.min(h);
let py0 = mid.saturating_sub(pkt_h / 2);
draw::fill_rect(grid, x0, py0, pkt_w, pkt_h);
if x0 + pkt_w < w && py0 < h {
}
let t_color = p as f32 / max_packets.max(1) as f32;
let color = ctx.palette.sample(t_color);
let cx0 = (x0 / 2).min(cells_w.saturating_sub(1));
let cx1 = ((x0 + pkt_w) / 2).min(cells_w.saturating_sub(1));
for cy in 0..cells_h {
draw::tint_row(grid, cy, cx0, cx1, color);
}
}
let dev_cells = (dev_w / 2).max(1);
let src_color = ctx.palette.sample(0.0);
let dst_color = ctx.palette.sample(1.0);
for cy in 0..cells_h {
draw::tint_row(grid, cy, 0, dev_cells.saturating_sub(1), src_color);
draw::tint_row(
grid,
cy,
cells_w.saturating_sub(dev_cells),
cells_w.saturating_sub(1),
dst_color,
);
}
Ok(())
}
}
struct GearTrain;
impl ProgressStyle for GearTrain {
fn name(&self) -> &str {
"gear-train"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"Interlocking gear train: large and small gears rotate in opposite directions"
}
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 draw_gear = |grid: &mut BrailleGrid,
cx: i32,
cy: i32,
r: usize,
n_teeth: usize,
angle_offset: f32| {
if r == 0 {
return;
}
let tooth_len = (r / 4).max(1) as f32;
let r_f = r as f32;
let steps = (2.0 * PI * r_f) as usize + 8;
for i in 0..=steps {
let theta = i as f32 / steps as f32 * 2.0 * PI + angle_offset;
let tooth_phase = (theta * n_teeth as f32 / (2.0 * PI)).fract();
let tooth_bump = if tooth_phase < 0.25 || tooth_phase > 0.75 {
tooth_len
} else {
0.0
};
let r_here = r_f + tooth_bump;
let dx = (theta.cos() * r_here) as i32;
let dy = (theta.sin() * r_here * 0.5) as i32;
draw::dot_i(grid, cx + dx, cy + dy);
}
draw::dot_i(grid, cx, cy);
};
let big_r = (h * 3 / 8).max(2);
let small_r = (big_r / 2).max(1);
let big_cx = (big_r + 2) as i32;
let big_cy = (h / 2) as i32;
let mesh_gap = 1i32; let small_cx = big_cx + big_r as i32 + small_r as i32 + mesh_gap;
let small_cy = big_cy;
let omega_big = ctx.eased * 1.5;
let big_angle = ctx.time * omega_big;
let gear_ratio = big_r as f32 / small_r.max(1) as f32;
let small_angle = -ctx.time * omega_big * gear_ratio;
let n_teeth_big = (big_r / 2).max(4).min(16);
let n_teeth_small = (small_r / 2).max(3).min(8);
draw_gear(grid, big_cx, big_cy, big_r, n_teeth_big, big_angle);
if small_cx + small_r as i32 + 2 < w as i32 {
draw_gear(
grid,
small_cx,
small_cy,
small_r,
n_teeth_small,
small_angle,
);
}
let tiny_r = (small_r / 2).max(1);
let tiny_cx = small_cx + small_r as i32 + tiny_r as i32 + mesh_gap;
let tiny_angle = -small_angle * (small_r as f32 / tiny_r.max(1) as f32);
if tiny_cx + tiny_r as i32 + 2 < w as i32 {
draw_gear(
grid,
tiny_cx,
small_cy,
tiny_r,
(tiny_r / 2).max(3).min(6),
tiny_angle,
);
}
for cx_c in 0..cells_w {
let t = cx_c as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t);
for cy_c in 0..cells_h {
draw::tint_row(grid, cy_c, cx_c, cx_c, color);
}
}
Ok(())
}
}
struct EinkRefresh;
impl ProgressStyle for EinkRefresh {
fn name(&self) -> &str {
"eink-refresh"
}
fn theme(&self) -> &str {
"gadgets"
}
fn describe(&self) -> &str {
"E-ink refresh: flashes full black then redraws content row-by-row via shade blocks"
}
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 (w, h) = draw::dot_dims(grid);
let flash_phase = 0.15f32;
if ctx.eased < flash_phase {
draw::fill_rect(grid, 0, 0, w, h);
let color = ctx.palette.sample(0.0);
draw::tint_row(grid, 0, 0, cells_w.saturating_sub(1), color);
} else {
let draw_frac = (ctx.eased - flash_phase) / (1.0 - flash_phase);
let total_cells = cells_w * cells_h;
let cells_drawn = ((draw_frac * total_cells as f32) as usize).min(total_cells);
let full_rows = cells_drawn / cells_w.max(1);
let partial_cols = cells_drawn % cells_w.max(1);
for cy in 0..full_rows.min(cells_h) {
for cx in 0..cells_w {
let linear = cy * cells_w + cx;
let h_val = hash(linear as u32 * 13 + 7);
let shade_level = match h_val % 5 {
0 => 1, 1 => 2, 2 => 3, 3 | 4 => 4, _ => 4,
};
draw::shade(grid, cx, cy, shade_level);
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t * 0.6 + 0.2);
draw::tint_row(grid, cy, cx, cx, color);
}
}
let partial_row = full_rows.min(cells_h.saturating_sub(1));
if full_rows < cells_h {
for cx in 0..partial_cols.min(cells_w) {
let linear = partial_row * cells_w + cx;
let h_val = hash(linear as u32 * 13 + 7);
let shade_level = (h_val % 4 + 1) as usize;
draw::shade(grid, cx, partial_row, shade_level);
let t = cx as f32 / cells_w.max(1) as f32;
let color = ctx.palette.sample(t * 0.5 + 0.3);
draw::tint_row(grid, partial_row, cx, cx, color);
}
if partial_cols < cells_w {
let blink = (ctx.time * 4.0).fract() > 0.5;
if blink {
let (_, dot_h) = draw::dot_dims(grid);
let row_y = (partial_row * 4 + 3).min(dot_h.saturating_sub(1));
draw::dot(grid, partial_cols * 2, row_y);
}
}
}
}
Ok(())
}
}