#![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::color::schemes::rainbow;
use dotmax::primitives::draw_line_colored;
use dotmax::{BrailleGrid, Color, TerminalRenderer};
use std::f64::consts::PI;
use std::io::{stdout, Write};
use std::time::Duration;
const WIDTH: usize = 80;
const HEIGHT: usize = 24;
const PRIMARY_AMPLITUDE: f64 = 35.0; const PRIMARY_FREQUENCY: f64 = 0.03; const SECONDARY_AMPLITUDE: f64 = 15.0; const SECONDARY_FREQUENCY: f64 = 0.07; const SCROLL_SPEED: f64 = 0.15; const TARGET_FPS: u32 = 30;
fn wave_y(x: f64, phase: f64, amplitude: f64, frequency: f64, center_y: f64) -> f64 {
amplitude.mul_add((x.mul_add(frequency, phase)).sin(), center_y)
}
fn draw_wave(
grid: &mut BrailleGrid,
phase: f64,
amplitude: f64,
frequency: f64,
center_y: f64,
color: Color,
) -> Result<(), dotmax::DotmaxError> {
let dot_width = WIDTH * 2;
let mut prev_x: Option<i32> = None;
let mut prev_y: Option<i32> = None;
for x in 0..dot_width {
let y = wave_y(x as f64, phase, amplitude, frequency, center_y);
let dot_height = HEIGHT * 4;
let clamped_y = y.clamp(0.0, (dot_height - 1) as f64) as i32;
if let (Some(px), Some(py)) = (prev_x, prev_y) {
draw_line_colored(grid, px, py, x as i32, clamped_y, color, None)?;
}
prev_x = Some(x as i32);
prev_y = Some(clamped_y);
}
Ok(())
}
fn draw_grid_lines(grid: &mut BrailleGrid) -> Result<(), dotmax::DotmaxError> {
let dot_width = WIDTH * 2;
let dot_height = HEIGHT * 4;
let center_y = dot_height / 2;
for x in (0..dot_width).step_by(4) {
grid.set_dot(x, center_y)?;
}
for x in (0..dot_width).step_by(8) {
grid.set_dot(x, 4)?;
grid.set_dot(x, dot_height - 5)?;
}
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(TARGET_FPS);
let scheme = rainbow();
let mut frame: u64 = 0;
let center_y = (HEIGHT * 4 / 2) as f64;
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 phase = frame as f64 * SCROLL_SPEED;
draw_grid_lines(&mut grid)?;
let primary_color = scheme.sample(0.6); draw_wave(
&mut grid,
phase,
PRIMARY_AMPLITUDE,
PRIMARY_FREQUENCY,
center_y,
primary_color,
)?;
let secondary_color = scheme.sample(0.0); draw_wave(
&mut grid,
phase * 1.5, SECONDARY_AMPLITUDE,
SECONDARY_FREQUENCY,
center_y,
secondary_color,
)?;
let tertiary_color = scheme.sample(0.33); draw_wave(
&mut grid,
phase * 0.7,
PRIMARY_AMPLITUDE * 0.5,
PRIMARY_FREQUENCY * 2.0,
center_y,
tertiary_color,
)?;
renderer.render(&grid)?;
execute!(
stdout,
MoveTo(0, HEIGHT as u16 + 1),
Print(format!(
"Waveform Demo | Frame: {:5} | Phase: {:6.2} | FPS: {:5.1} | [q]uit ",
frame,
phase % (2.0 * PI),
timer.actual_fps()
))
)?;
stdout.flush()?;
frame += 1;
timer.wait_for_next_frame();
}
execute!(stdout, Show, Clear(ClearType::All), MoveTo(0, 0))?;
renderer.cleanup()?;
disable_raw_mode()?;
println!("Waveform Animation Complete!");
println!("Rendered {frame} frames of scrolling waveforms.");
println!("\nFeatures demonstrated:");
println!("- Line drawing primitives (draw_line_colored)");
println!("- Color schemes from Epic 5 (rainbow)");
println!("- Sine wave physics with phase shifting");
println!("- Multiple overlapping waves");
Ok(())
}