clock-rs 0.2.1

A modern, terminal-based digital clock
pub mod counter;
pub mod mode;
pub mod time_zone;

use std::{
    io::{BufWriter, StdoutLock, Write},
    time::Duration,
};

use crate::{
    character::Character, clock::mode::ClockMode, color::Color, config::Config, error::Error,
    position::Position,
};

#[derive(Default)]
pub struct Padding {
    pub top: u16,
    clock: String,
    text: String,
}

pub struct Clock {
    pub mode: ClockMode,
    pub padding: Padding,
    pub interval: Duration,
    pub x_pos: Position,
    pub y_pos: Position,
    pub color: Color,
    pub use_12h: bool,
    pub hide_seconds: bool,
    pub blink: bool,
    pub bold: bool,
}

impl Clock {
    const WIDTH: u16 = 51;
    const WIDTH_NO_SECONDS: u16 = 32;
    const HEIGHT: u16 = 7;
    const SUFFIX_LEN: u16 = 5;
    const AM_SUFFIX: &'static str = " [AM]";
    const PM_SUFFIX: &'static str = " [PM]";

    pub fn new(config: Config, mode: ClockMode) -> Self {
        Self {
            mode,
            padding: Padding::default(),
            interval: Duration::from_millis(config.general.interval),
            x_pos: config.position.x,
            y_pos: config.position.y,
            color: config.general.color,
            use_12h: config.date.use_12h,
            hide_seconds: config.date.hide_seconds,
            blink: config.general.blink,
            bold: config.general.bold,
        }
    }

    pub fn update_padding(&mut self, width: u16, height: u16) -> Result<(), Error> {
        let clock_width = self.width();
        let text_len = self.mode.text(clock_width)?.len() as u16
            + if self.use_12h { Self::SUFFIX_LEN } else { 0 };

        let half_width = clock_width / 2;

        let column = self.x_pos.calculate(width, half_width);
        self.padding.top = self.y_pos.calculate(height, Self::HEIGHT / 2);

        self.padding.clock = " ".repeat(column as usize);
        self.padding.text = format!(
            "{}{}",
            self.padding.clock,
            " ".repeat(half_width.saturating_sub(text_len / 2) as usize)
        );

        Ok(())
    }

    pub fn is_too_large(&self, width: u16, height: u16) -> bool {
        self.width() + 1 >= width || Self::HEIGHT + 1 >= height
    }

    fn width(&self) -> u16 {
        if self.hide_seconds {
            return Self::WIDTH_NO_SECONDS;
        }

        Self::WIDTH
    }

    pub fn fmt(&self, w: &mut BufWriter<StdoutLock<'_>>) -> Result<(), Error> {
        let mut text = self.mode.text(self.width())?;
        let (mut hour, minute, second) = self.mode.get_time();

        if matches!(self.mode, ClockMode::Time { .. }) && self.use_12h {
            let suffix = if hour < 12 {
                Self::AM_SUFFIX
            } else {
                Self::PM_SUFFIX
            };

            text.push_str(suffix);

            if hour > 12 {
                hour -= 12;
            } else if hour == 0 {
                hour = 12;
            }
        }

        let color = &self.color;

        for row in 0..5 {
            let colon_character = if self.blink && (second & 1 == 1) {
                Character::Empty
            } else {
                Character::Colon
            };

            let colon = colon_character.fmt(color, row);
            let h0 = Character::Num(hour / 10).fmt(color, row);
            let h1 = Character::Num(hour % 10).fmt(color, row);
            let m0 = Character::Num(minute / 10).fmt(color, row);
            let m1 = Character::Num(minute % 10).fmt(color, row);

            write!(w, "{}{h0}{h1}{colon}{m0}{m1}", self.padding.clock)?;

            if !self.hide_seconds {
                let s0 = Character::Num(second / 10).fmt(color, row);
                let s1 = Character::Num(second % 10).fmt(color, row);

                write!(w, "{colon}{s0}{s1}")?;
            }

            writeln!(w, "\r")?;
        }

        let bold_escape_str = if self.bold { Color::BOLD } else { "" };

        writeln!(
            w,
            "\n{bold_escape_str}{}{}{text}",
            self.padding.text,
            self.color.foreground()
        )?;

        Ok(())
    }
}