#![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::primitives::{draw_circle, 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 = 50;
const HEIGHT: usize = 25;
const CLOCK_RADIUS: u32 = 40; const HOUR_HAND_LENGTH: f64 = 20.0; const MINUTE_HAND_LENGTH: f64 = 32.0; const SECOND_HAND_LENGTH: f64 = 38.0;
const HOUR_HAND_COLOR: Color = Color { r: 255, g: 255, b: 255 }; const MINUTE_HAND_COLOR: Color = Color { r: 200, g: 200, b: 255 }; const SECOND_HAND_COLOR: Color = Color { r: 255, g: 100, b: 100 }; const MARKER_COLOR: Color = Color { r: 255, g: 255, b: 200 };
fn get_current_time() -> (u32, u32, u32) {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");
let total_secs = now.as_secs();
let secs_in_day = total_secs % (24 * 60 * 60);
let hours = ((secs_in_day / 3600) % 12) as u32; let minutes = ((secs_in_day % 3600) / 60) as u32;
let seconds = (secs_in_day % 60) as u32;
(hours, minutes, seconds)
}
fn time_to_angle(value: u32, max_value: u32) -> f64 {
let fraction = f64::from(value) / f64::from(max_value);
(fraction * 2.0).mul_add(PI, -PI / 2.0)
}
fn draw_hand(
grid: &mut BrailleGrid,
center_x: i32,
center_y: i32,
angle: f64,
length: f64,
color: Color,
) -> Result<(), dotmax::DotmaxError> {
let end_x = center_x + length.mul_add(angle.cos(), 0.0) as i32;
let end_y = center_y + length.mul_add(angle.sin(), 0.0) as i32;
draw_line_colored(grid, center_x, center_y, end_x, end_y, color, None)?;
Ok(())
}
fn draw_hour_markers(
grid: &mut BrailleGrid,
center_x: i32,
center_y: i32,
radius: u32,
) -> Result<(), dotmax::DotmaxError> {
let radius_f64 = f64::from(radius);
for hour in 0..12 {
let angle = time_to_angle(hour, 12);
let outer_r = radius_f64 - 2.0;
let inner_r = radius_f64 - 6.0;
let outer_x = center_x + outer_r.mul_add(angle.cos(), 0.0) as i32;
let outer_y = center_y + outer_r.mul_add(angle.sin(), 0.0) as i32;
let inner_x = center_x + inner_r.mul_add(angle.cos(), 0.0) as i32;
let inner_y = center_y + inner_r.mul_add(angle.sin(), 0.0) as i32;
draw_line_colored(grid, inner_x, inner_y, outer_x, outer_y, MARKER_COLOR, None)?;
}
Ok(())
}
fn draw_center(
grid: &mut BrailleGrid,
center_x: i32,
center_y: i32,
) -> Result<(), dotmax::DotmaxError> {
for dx in -1..=1 {
for dy in -1..=1 {
let x = (center_x + dx) as usize;
let y = (center_y + dy) as usize;
let (w, h) = grid.dimensions();
if x < w * 2 && y < h * 4 {
grid.set_dot(x, y)?;
}
}
}
let cell_x = (center_x as usize) / 2;
let cell_y = (center_y as usize) / 4;
grid.set_cell_color(cell_x, cell_y, HOUR_HAND_COLOR)?;
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(1);
let center_x = (WIDTH * 2 / 2) as i32;
let center_y = (HEIGHT * 4 / 2) as i32;
loop {
if event::poll(Duration::from_millis(100))? {
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 (hours, minutes, seconds) = get_current_time();
let mut grid = BrailleGrid::new(WIDTH, HEIGHT)?;
draw_circle(&mut grid, center_x, center_y, CLOCK_RADIUS)?;
draw_hour_markers(&mut grid, center_x, center_y, CLOCK_RADIUS)?;
let hour_angle = time_to_angle(hours * 5 + minutes / 12, 60);
let minute_angle = time_to_angle(minutes, 60);
let second_angle = time_to_angle(seconds, 60);
draw_hand(
&mut grid,
center_x,
center_y,
hour_angle,
HOUR_HAND_LENGTH,
HOUR_HAND_COLOR,
)?;
draw_hand(
&mut grid,
center_x,
center_y,
minute_angle,
MINUTE_HAND_LENGTH,
MINUTE_HAND_COLOR,
)?;
draw_hand(
&mut grid,
center_x,
center_y,
second_angle,
SECOND_HAND_LENGTH,
SECOND_HAND_COLOR,
)?;
draw_center(&mut grid, center_x, center_y)?;
renderer.render(&grid)?;
execute!(
stdout,
MoveTo(0, HEIGHT as u16 + 1),
Print(format!(
" Analog Clock | {hours:02}:{minutes:02}:{seconds:02} | [q]uit "
))
)?;
stdout.flush()?;
timer.wait_for_next_frame();
}
execute!(stdout, Show, Clear(ClearType::All), MoveTo(0, 0))?;
renderer.cleanup()?;
disable_raw_mode()?;
println!("Analog Clock Demo Complete!");
println!("\nFeatures demonstrated:");
println!("- Circle drawing (clock face)");
println!("- Line drawing (clock hands)");
println!("- Color support (different hand colors)");
println!("- Real-time system time");
println!("- Low FPS mode (1 FPS for efficiency)");
Ok(())
}