#![allow(
clippy::uninlined_format_args,
clippy::doc_markdown,
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::too_many_lines,
clippy::unnecessary_wraps
)]
use crossterm::{
cursor::{Hide, MoveTo, Show},
event::{self, Event, KeyCode, KeyEvent},
execute,
style::Print,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
};
use dotmax::animation::{DifferentialRenderer, FrameTimer};
use dotmax::{BrailleGrid, TerminalRenderer};
use std::io::{stdout, Write};
use std::time::Duration;
const WIDTH: usize = 80;
const HEIGHT: usize = 24;
const TARGET_FPS: u32 = 60;
struct Ball {
x: f64,
y: f64,
vx: f64,
vy: f64,
radius: usize,
}
impl Ball {
const fn new() -> Self {
Self {
x: 40.0,
y: 48.0,
vx: 3.0,
vy: 2.5,
radius: 4,
}
}
fn update(&mut self) {
self.x += self.vx;
self.y += self.vy;
let max_x = (WIDTH * 2 - 1) as f64;
let max_y = (HEIGHT * 4 - 1) as f64;
if self.x - self.radius as f64 <= 1.0 || self.x + self.radius as f64 >= max_x - 1.0 {
self.vx = -self.vx;
self.x = self.x.clamp(self.radius as f64 + 1.0, max_x - self.radius as f64 - 1.0);
}
if self.y - self.radius as f64 <= 1.0 || self.y + self.radius as f64 >= max_y - 1.0 {
self.vy = -self.vy;
self.y = self.y.clamp(self.radius as f64 + 1.0, max_y - self.radius as f64 - 1.0);
}
}
fn draw(&self, grid: &mut BrailleGrid) {
let cx = self.x as isize;
let cy = self.y as isize;
let r = self.radius as isize;
for dy in -r..=r {
for dx in -r..=r {
if dx * dx + dy * dy <= r * r {
let px = (cx + dx) as usize;
let py = (cy + dy) as usize;
let _ = grid.set_dot(px, py);
}
}
}
}
}
fn draw_border(grid: &mut BrailleGrid) {
let dot_width = WIDTH * 2;
let dot_height = HEIGHT * 4;
for x in 0..dot_width {
let _ = grid.set_dot(x, 0);
let _ = grid.set_dot(x, dot_height - 1);
}
for y in 0..dot_height {
let _ = grid.set_dot(0, y);
let _ = grid.set_dot(dot_width - 1, y);
}
}
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 terminal = TerminalRenderer::new()?;
let mut diff_renderer = DifferentialRenderer::new();
let mut timer = FrameTimer::new(TARGET_FPS);
let mut ball = Ball::new();
let mut frame_count = 0u64;
let mut use_differential = true;
let mut last_changed_cells: usize;
let mut prev_frame: Option<BrailleGrid> = None;
loop {
if event::poll(Duration::from_millis(0))? {
if let Event::Key(KeyEvent { code, .. }) = event::read()? {
match code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Char('c') if event::poll(Duration::ZERO).is_ok() => break,
KeyCode::Char('f') => {
use_differential = !use_differential;
diff_renderer.invalidate(); }
_ => {}
}
}
}
let mut grid = BrailleGrid::new(WIDTH, HEIGHT)?;
draw_border(&mut grid);
ball.update();
ball.draw(&mut grid);
if let Some(ref prev) = prev_frame {
last_changed_cells = diff_renderer.count_changed_cells(&grid, prev);
} else {
last_changed_cells = WIDTH * HEIGHT; }
if use_differential {
diff_renderer.render_diff(&grid, &mut terminal)?;
} else {
terminal.render(&grid)?;
}
prev_frame = Some(grid);
let total_cells = WIDTH * HEIGHT;
let reduction = if last_changed_cells > 0 {
((total_cells - last_changed_cells) as f64 / total_cells as f64) * 100.0
} else {
100.0
};
execute!(
stdout,
MoveTo(0, HEIGHT as u16),
Print(format!(
"Frame: {:6} | FPS: {:5.1} | Mode: {:12} | Changed: {:4}/{:4} cells | I/O Reduction: {:5.1}% | [f]toggle [q]quit",
frame_count,
timer.actual_fps(),
if use_differential { "Differential" } else { "Full" },
last_changed_cells,
total_cells,
reduction
))
)?;
stdout.flush()?;
frame_count += 1;
timer.wait_for_next_frame();
}
execute!(stdout, Show, Clear(ClearType::All), MoveTo(0, 0))?;
terminal.cleanup()?;
disable_raw_mode()?;
println!("Differential Rendering Demo Complete!");
println!("=====================================");
println!("Total frames rendered: {frame_count}");
println!("Average FPS: {:.1}", timer.actual_fps());
println!();
println!("Key benefits of differential rendering:");
println!("- Only changed cells are sent to terminal");
println!("- For typical animations with small moving objects: 60-95% I/O reduction");
println!("- Lower CPU usage and smoother animations at high frame rates");
Ok(())
}