use std::env;
use std::fmt::Write as _;
use std::io;
use std::io::Write;
use std::thread;
use std::time::Duration;
use chrono::Local;
use chrono::Timelike as _;
use clap::Parser;
use crate::brush;
use crate::brush::Brush;
use crate::brush::Color;
use crate::font;
use crate::time;
use crate::time::Date;
use crate::time::Time;
#[derive(Parser, Debug)]
#[clap(name = "tock", about = "A digital clock for the terminal.")]
pub struct Configuration {
#[clap(short, long, default_value_t = 0)]
x: u16,
#[clap(short, long, default_value_t = 0)]
y: u16,
#[clap(short = 'W', long, default_value_t = 2)]
width: u16,
#[clap(short = 'H', long, default_value_t = 1)]
height: u16,
#[clap(short, long)]
second: bool,
#[clap(short, long)]
military: bool,
#[clap(short, long)]
center: bool,
#[clap(short = 'C', long, default_value = "2")]
color: Color,
#[clap(short, long, default_value = "%F | %Z")]
format: String,
}
#[derive(Debug)]
pub struct Clock {
configuration: Configuration,
date: Date,
time: Time,
brush: Brush,
buffer: String,
}
impl Clock {
pub fn new(mut configuration: Configuration) -> Self {
let zone = env::var("TZ").unwrap_or_else(|_| String::from("Local"));
configuration.format = configuration.format.replace("%Z", &zone);
Clock {
date: Date::blank(),
time: Time::blank(configuration.second, configuration.military),
brush: Brush::new(configuration.color),
buffer: String::new(),
configuration,
}
}
#[cfg_attr(not(feature = "interactive"), allow(unused))]
pub fn toggle_second(&mut self) {
self.configuration.second ^= true;
}
#[cfg_attr(not(feature = "interactive"), allow(unused))]
pub fn toggle_military(&mut self) {
self.configuration.military ^= true;
}
#[cfg_attr(not(feature = "interactive"), allow(unused))]
pub fn set_color(&mut self, color: Color) {
self.brush.dip(color)
}
pub fn resize(&mut self, (w, h): (u16, u16)) {
if self.configuration.center {
self.configuration.x = w / 2 - self.width() / 2;
self.configuration.y = h / 2 - self.height() / 2;
}
}
pub fn sync(&self) {
let start = Local::now().nanosecond() as u64;
let delay = Duration::from_nanos(1_000_000_000 - start);
thread::sleep(delay);
}
pub fn update<W: Write>(&mut self, mut out: W) -> io::Result<()> {
let (date, time) = time::now(self.configuration.second, self.configuration.military);
let draw = self.time ^ time;
for digit in 0..self.digits() {
if draw[digit] == 0 {
continue;
}
let dx =
self.configuration.x + ((font::W + 1) * self.configuration.width * digit as u16);
let dy = self.configuration.y;
let mut mask = 0b1000_0000_0000_0000_u16;
for i in 0..15 {
mask >>= 1;
if draw[digit] & mask == 0 {
continue;
}
let x = i % font::W * self.configuration.width + dx;
let y = i / font::W * self.configuration.height + dy;
self.brush.set(time[digit] & mask > 0);
self.buffer.clear();
self.write_row_buffer();
self.render_row_buffer(x, y, &mut out)?;
}
}
if date != self.date {
self.draw_date(&date, &mut out)?;
}
out.flush()?;
self.date = date;
self.time = time;
Ok(())
}
pub fn reset<W: Write>(&mut self, mut out: W) -> io::Result<()> {
let (date, time) = time::now(self.configuration.second, self.configuration.military);
self.brush.raise();
write!(out, "{}{}", self.brush, brush::CLEAR)?;
for y in 0..font::H {
self.buffer.clear();
for digit in 0..self.digits() {
let mut mask = 1 << ((font::H - y) * font::W);
for _ in 0..font::W {
mask >>= 1;
self.brush.set(time[digit] & mask > 0);
self.write_row_buffer();
}
self.brush.raise();
self.write_row_buffer();
}
let x = self.configuration.x;
let y = self.configuration.y + y * self.configuration.height;
self.render_row_buffer(x, y, &mut out)?;
}
self.draw_date(&date, &mut out)?;
out.flush()?;
self.date = date;
self.time = time;
Ok(())
}
fn draw_date<W: Write>(&mut self, date: &Date, out: &mut W) -> io::Result<()> {
self.brush.raise();
self.buffer.clear();
date.format(&self.configuration.format, &mut self.buffer);
let date_x = self.configuration.x + self.width() / 2 - self.buffer.len() as u16 / 2;
let date_y = self.configuration.y + self.height() + 1;
let goto = brush::Move(date_x, date_y);
write!(out, "{}{}{}", self.brush, goto, self.buffer)
}
fn write_row_buffer(&mut self) {
write!(
&mut self.buffer,
"{}{:2$}",
self.brush, " ", self.configuration.width as usize
)
.expect("[INTERNAL ERROR]: writing into String failed");
}
fn render_row_buffer<W: Write>(&self, x: u16, y: u16, mut out: W) -> io::Result<()> {
for i in 0..self.configuration.height {
write!(out, "{}{}", brush::Move(x, y + i), self.buffer)?;
}
Ok(())
}
fn digits(&self) -> usize {
Time::width(self.configuration.second, self.configuration.military)
}
pub fn width(&self) -> u16 {
(self.configuration.width * (font::W + 1)) * self.digits() as u16 - 1
}
pub fn height(&self) -> u16 {
self.configuration.height * font::H
}
}