use crate::style::Color;
use crate::widget::{RenderContext, View, WidgetProps};
use crate::{impl_props_builders, impl_styled_view};
const DIGIT_PATTERNS_BLOCK: [[&str; 5]; 10] = [
["███", "█ █", "█ █", "█ █", "███"],
[" █", " █", " █", " █", " █"],
["███", " █", "███", "█ ", "███"],
["███", " █", "███", " █", "███"],
["█ █", "█ █", "███", " █", " █"],
["███", "█ ", "███", " █", "███"],
["███", "█ ", "███", "█ █", "███"],
["███", " █", " █", " █", " █"],
["███", "█ █", "███", "█ █", "███"],
["███", "█ █", "███", " █", "███"],
];
const DIGIT_PATTERNS_THIN: [[&str; 5]; 10] = [
["┌─┐", "│ │", "│ │", "│ │", "└─┘"],
[" │", " │", " │", " │", " │"],
["──┐", " │", "┌─┘", "│ ", "└──"],
["──┐", " │", "──┤", " │", "──┘"],
["│ │", "│ │", "└─┤", " │", " │"],
["┌──", "│ ", "└─┐", " │", "──┘"],
["┌──", "│ ", "├─┐", "│ │", "└─┘"],
["──┐", " │", " │", " │", " │"],
["┌─┐", "│ │", "├─┤", "│ │", "└─┘"],
["┌─┐", "│ │", "└─┤", " │", "──┘"],
];
const DIGIT_PATTERNS_ASCII: [[&str; 5]; 10] = [
["+-+", "| |", "| |", "| |", "+-+"],
[" |", " |", " |", " |", " |"],
["--+", " |", "+-+", "| ", "+--"],
["--+", " |", "--+", " |", "--+"],
["| |", "| |", "+-+", " |", " |"],
["+--", "| ", "+-+", " |", "--+"],
["+--", "| ", "+-+", "| |", "+-+"],
["--+", " |", " |", " |", " |"],
["+-+", "| |", "+-+", "| |", "+-+"],
["+-+", "| |", "+-+", " |", "--+"],
];
const DIGIT_PATTERNS_BRAILLE: [[&str; 4]; 10] = [
["⣰⣆", "⡇⢸", "⡇⢸", "⠈⠉"],
[" ⡆", " ⡇", " ⡇", " ⠁"],
["⠤⡤", " ⡰", "⡰ ", "⠧⠤"],
["⠤⡤", " ⡤", " ⡤", "⠤⠴"],
["⡆⡆", "⡧⡦", " ⡇", " ⠁"],
["⡤⠤", "⡤⠤", " ⢸", "⠤⠴"],
["⣰⠆", "⡧⠤", "⡇⢸", "⠈⠉"],
["⠤⡤", " ⡰", " ⡇", " ⠁"],
["⣰⣆", "⡧⡦", "⡇⢸", "⠈⠉"],
["⣰⣆", "⡧⡦", " ⢸", "⠤⠴"],
];
const COLON_BLOCK: [&str; 5] = [" ", "█", " ", "█", " "];
const COLON_THIN: [&str; 5] = [" ", "●", " ", "●", " "];
const COLON_ASCII: [&str; 5] = [" ", "o", " ", "o", " "];
const COLON_BRAILLE: [&str; 4] = [" ", "⠂", "⠂", " "];
const DOT_BLOCK: [&str; 5] = [" ", " ", " ", " ", "█"];
const DOT_THIN: [&str; 5] = [" ", " ", " ", " ", "●"];
const DOT_ASCII: [&str; 5] = [" ", " ", " ", " ", "o"];
const DOT_BRAILLE: [&str; 4] = [" ", " ", " ", "⠂"];
const MINUS_BLOCK: [&str; 5] = [" ", " ", "███", " ", " "];
const MINUS_THIN: [&str; 5] = [" ", " ", "───", " ", " "];
const MINUS_ASCII: [&str; 5] = [" ", " ", "---", " ", " "];
const MINUS_BRAILLE: [&str; 4] = [" ", "⠤⠤", " ", " "];
const SPACE_BLOCK: [&str; 5] = [" ", " ", " ", " ", " "];
const SPACE_BRAILLE: [&str; 4] = [" ", " ", " ", " "];
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum DigitStyle {
#[default]
Block,
Thin,
Ascii,
Braille,
}
#[derive(Clone, Debug)]
pub struct Digits {
value: String,
style: DigitStyle,
fg: Option<Color>,
bg: Option<Color>,
prefix: Option<String>,
suffix: Option<String>,
separator: Option<char>,
min_width: Option<usize>,
leading_zeros: bool,
props: WidgetProps,
}
impl Digits {
pub fn new(value: impl ToString) -> Self {
Self {
value: value.to_string(),
style: DigitStyle::default(),
fg: None,
bg: None,
prefix: None,
suffix: None,
separator: None,
min_width: None,
leading_zeros: false,
props: WidgetProps::new(),
}
}
pub fn from_int(value: i64) -> Self {
Self::new(value)
}
pub fn from_float(value: f64, decimals: usize) -> Self {
Self::new(format!("{:.prec$}", value, prec = decimals))
}
pub fn time(hours: u32, minutes: u32, seconds: u32) -> Self {
Self::new(format!("{:02}:{:02}:{:02}", hours, minutes, seconds))
}
pub fn clock(hours: u32, minutes: u32) -> Self {
Self::new(format!("{:02}:{:02}", hours, minutes))
}
pub fn timer(total_seconds: u64) -> Self {
let hours = total_seconds / 3600;
let minutes = (total_seconds % 3600) / 60;
let seconds = total_seconds % 60;
if hours > 0 {
Self::time(hours as u32, minutes as u32, seconds as u32)
} else {
Self::new(format!("{:02}:{:02}", minutes, seconds))
}
}
pub fn style(mut self, style: DigitStyle) -> Self {
self.style = style;
self
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix = Some(prefix.into());
self
}
pub fn suffix(mut self, suffix: impl Into<String>) -> Self {
self.suffix = Some(suffix.into());
self
}
pub fn separator(mut self, sep: char) -> Self {
self.separator = Some(sep);
self
}
pub fn min_width(mut self, width: usize) -> Self {
self.min_width = Some(width);
self
}
pub fn leading_zeros(mut self, show: bool) -> Self {
self.leading_zeros = show;
self
}
pub fn height(&self) -> usize {
match self.style {
DigitStyle::Braille => 4,
_ => 5,
}
}
pub fn digit_width(&self) -> usize {
match self.style {
DigitStyle::Braille => 2,
_ => 3,
}
}
fn get_char_pattern(&self, c: char) -> Vec<&'static str> {
match self.style {
DigitStyle::Block => self.get_block_pattern(c),
DigitStyle::Thin => self.get_thin_pattern(c),
DigitStyle::Ascii => self.get_ascii_pattern(c),
DigitStyle::Braille => self.get_braille_pattern(c),
}
}
fn get_block_pattern(&self, c: char) -> Vec<&'static str> {
match c {
'0'..='9' => DIGIT_PATTERNS_BLOCK[(c as usize) - ('0' as usize)].to_vec(),
':' => COLON_BLOCK.to_vec(),
'.' => DOT_BLOCK.to_vec(),
'-' => MINUS_BLOCK.to_vec(),
_ => SPACE_BLOCK.to_vec(),
}
}
fn get_thin_pattern(&self, c: char) -> Vec<&'static str> {
match c {
'0'..='9' => DIGIT_PATTERNS_THIN[(c as usize) - ('0' as usize)].to_vec(),
':' => COLON_THIN.to_vec(),
'.' => DOT_THIN.to_vec(),
'-' => MINUS_THIN.to_vec(),
_ => SPACE_BLOCK.to_vec(),
}
}
fn get_ascii_pattern(&self, c: char) -> Vec<&'static str> {
match c {
'0'..='9' => DIGIT_PATTERNS_ASCII[(c as usize) - ('0' as usize)].to_vec(),
':' => COLON_ASCII.to_vec(),
'.' => DOT_ASCII.to_vec(),
'-' => MINUS_ASCII.to_vec(),
_ => SPACE_BLOCK.to_vec(),
}
}
fn get_braille_pattern(&self, c: char) -> Vec<&'static str> {
match c {
'0'..='9' => DIGIT_PATTERNS_BRAILLE[(c as usize) - ('0' as usize)].to_vec(),
':' => COLON_BRAILLE.to_vec(),
'.' => DOT_BRAILLE.to_vec(),
'-' => MINUS_BRAILLE.to_vec(),
_ => SPACE_BRAILLE.to_vec(),
}
}
pub fn format_value(&self) -> String {
let mut result = self.value.clone();
if let Some(width) = self.min_width {
if result.len() < width {
let pad = "0".repeat(width - result.len());
result = format!("{}{}", pad, result);
}
}
if let Some(sep) = self.separator {
result = self.add_thousands_separator(&result, sep);
}
result
}
fn add_thousands_separator(&self, s: &str, sep: char) -> String {
let (sign, num) = if let Some(rest) = s.strip_prefix('-') {
("-", rest)
} else {
("", s)
};
let parts: Vec<&str> = num.split('.').collect();
let integer_part = parts[0];
let decimal_part = parts.get(1);
let mut result = String::new();
for (i, c) in integer_part.chars().rev().enumerate() {
if i > 0 && i % 3 == 0 {
result.push(sep);
}
result.push(c);
}
let integer_with_sep: String = result.chars().rev().collect();
match decimal_part {
Some(dec) => format!("{}{}.{}", sign, integer_with_sep, dec),
None => format!("{}{}", sign, integer_with_sep),
}
}
pub fn render_lines(&self) -> Vec<String> {
let value = self.format_value();
let height = self.height();
let mut lines: Vec<String> = vec![String::new(); height];
for c in value.chars() {
let pattern = self.get_char_pattern(c);
for (i, row) in pattern.iter().enumerate() {
if i < height {
lines[i].push_str(row);
lines[i].push(' '); }
}
}
lines
}
}
impl View for Digits {
crate::impl_view_meta!("Digits");
fn render(&self, ctx: &mut RenderContext) {
use crate::widget::stack::vstack;
use crate::widget::Text;
let lines = self.render_lines();
let mut stack = vstack();
for line in lines {
let mut text = Text::new(line);
if let Some(fg) = self.fg {
text = text.fg(fg);
}
if let Some(bg) = self.bg {
text = text.bg(bg);
}
stack = stack.child(text);
}
if self.prefix.is_some() || self.suffix.is_some() {
let label = format!(
"{}{}{}",
self.prefix.as_deref().unwrap_or(""),
self.value,
self.suffix.as_deref().unwrap_or("")
);
let mut text = Text::new(label);
if let Some(fg) = self.fg {
text = text.fg(fg);
}
stack = stack.child(text);
}
stack.render(ctx);
}
}
impl_styled_view!(Digits);
impl_props_builders!(Digits);
pub fn digits(value: impl ToString) -> Digits {
Digits::new(value)
}
pub fn clock(hours: u32, minutes: u32) -> Digits {
Digits::clock(hours, minutes)
}
pub fn timer(seconds: u64) -> Digits {
Digits::timer(seconds)
}