#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::{self, Event, KeyCode, KeyModifiers},
execute,
style::Print,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
use dotmax::animation::FrameTimer;
use dotmax::{BrailleGrid, TerminalRenderer};
use std::f64::consts::PI;
use std::io::{stdout, Write};
use std::time::Duration;
const WIDTH: usize = 40;
const HEIGHT: usize = 12;
const SPINNER_RADIUS: f64 = 15.0;
const ROTATION_FPS: u32 = 10;
#[derive(Clone, Copy)]
enum SpinnerStyle {
Dot,
Arc,
Circle,
}
impl SpinnerStyle {
const fn next(self) -> Self {
match self {
Self::Dot => Self::Arc,
Self::Arc => Self::Circle,
Self::Circle => Self::Dot,
}
}
const fn name(self) -> &'static str {
match self {
Self::Dot => "Dot",
Self::Arc => "Arc",
Self::Circle => "Circle",
}
}
}
fn draw_spinner(
grid: &mut BrailleGrid,
center_x: usize,
center_y: usize,
angle: f64,
style: SpinnerStyle,
) -> Result<(), dotmax::DotmaxError> {
match style {
SpinnerStyle::Dot => {
draw_dot_at_angle(grid, center_x, center_y, SPINNER_RADIUS, angle)?;
for i in 1..=3 {
let trail_angle = f64::from(i).mul_add(-0.3, angle);
let trail_radius = SPINNER_RADIUS - f64::from(i);
if trail_radius > 0.0 {
draw_dot_at_angle(grid, center_x, center_y, trail_radius, trail_angle)?;
}
}
}
SpinnerStyle::Arc => {
for i in 0..8 {
let arc_angle = f64::from(i).mul_add(PI / 16.0, angle);
draw_dot_at_angle(grid, center_x, center_y, SPINNER_RADIUS, arc_angle)?;
}
}
SpinnerStyle::Circle => {
let num_dots: i32 = 16;
for i in 0..num_dots {
let dot_angle = (f64::from(i) * 2.0 * PI / f64::from(num_dots)) + angle;
let r = SPINNER_RADIUS - (f64::from(i) * 0.3).min(5.0);
if r > 0.0 {
draw_dot_at_angle(grid, center_x, center_y, r, dot_angle)?;
}
}
}
}
Ok(())
}
fn draw_dot_at_angle(
grid: &mut BrailleGrid,
center_x: usize,
center_y: usize,
radius: f64,
angle: f64,
) -> Result<(), dotmax::DotmaxError> {
let x = radius.mul_add(angle.cos(), center_x as f64);
let y = radius.mul_add(angle.sin(), center_y as f64);
let (width, height) = grid.dimensions();
let dot_width = width * 2;
let dot_height = height * 4;
if x >= 0.0 && x < dot_width as f64 && y >= 0.0 && y < dot_height as f64 {
grid.set_dot(x as usize, y as usize)?;
}
Ok(())
}
fn draw_loading_text(
grid: &mut BrailleGrid,
frame: u64,
) -> Result<(), dotmax::DotmaxError> {
let (width, _height) = grid.dimensions();
let dot_width = width * 2;
let base_y = 44;
let num_dots: usize = 6;
let active_dots = ((frame / 3) % (num_dots as u64 + 1)) as usize;
let start_x = dot_width / 2 - num_dots * 2;
for i in 0..num_dots {
if i < active_dots {
grid.set_dot(start_x + i * 4, base_y)?;
grid.set_dot(start_x + i * 4 + 1, base_y)?;
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, Clear(ClearType::All), Hide, MoveTo(0, 0))?;
let mut renderer = TerminalRenderer::new()?;
let mut timer = FrameTimer::new(ROTATION_FPS);
let mut frame: u64 = 0;
let mut style = SpinnerStyle::Dot;
let style_change_interval = 50;
let center_x = WIDTH * 2 / 2; let center_y = HEIGHT * 4 / 2 - 4;
loop {
if event::poll(Duration::from_millis(0))? {
if let Event::Key(key) = event::read()? {
if key.code == KeyCode::Char('q')
|| key.code == KeyCode::Esc
|| (key.code == KeyCode::Char('c')
&& key.modifiers.contains(KeyModifiers::CONTROL))
{
break;
}
}
}
let mut grid = BrailleGrid::new(WIDTH, HEIGHT)?;
let angle = (frame as f64 * 36.0).to_radians();
draw_spinner(&mut grid, center_x, center_y, angle, style)?;
draw_loading_text(&mut grid, frame)?;
renderer.render(&grid)?;
execute!(
stdout,
MoveTo(0, HEIGHT as u16 + 1),
Print(format!(
"Style: {:8} | Frame: {:4} | FPS: {:5.1} | [q]uit",
style.name(),
frame,
timer.actual_fps()
))
)?;
stdout.flush()?;
if frame > 0 && frame % style_change_interval == 0 {
style = style.next();
}
frame += 1;
timer.wait_for_next_frame();
}
execute!(stdout, Show, Clear(ClearType::All), MoveTo(0, 0))?;
renderer.cleanup()?;
disable_raw_mode()?;
println!("Loading Spinner Demo Complete!");
println!("Demonstrated {frame} frames across all spinner styles.");
Ok(())
}