use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Alignment, Rect};
use ratatui_core::widgets::Widget;
use crate::core::MinSize;
use crate::draw::{self, put, put_centered, st};
use crate::theme::{self, Rgb, Theme, Variant};
const GLYPHS: &str = " 0123456789+-^x:ABCDEF$£€()";
const GLYPH_DATA: &[&str] = &[
" ",
" ",
" ",
"╭─╮",
"│ │",
"╰─╯",
"╶╮ ",
" │ ",
"╶┴╴",
"╶─╮",
"┌─┘",
"╰─╴",
"╶─╮",
" ─┤",
"╶─╯",
"╷ ╷",
"╰─┤",
" ╵",
"╭─╴",
"╰─╮",
"╶─╯",
"╭─╴",
"├─╮",
"╰─╯",
"╶─┐",
" │",
" ╵",
"╭─╮",
"├─┤",
"╰─╯",
"╭─╮",
"╰─┤",
"╶─╯",
" ",
"╶┼╴",
" ",
" ",
"╶─╴",
" ",
" ^ ",
" ",
" ",
" ",
" × ",
" ",
" ",
":",
" ",
"╭─╮",
"├─┤",
"╵ ╵",
"┌─╮",
"├─┤",
"└─╯",
"╭─╮",
"│ ",
"╰─╯",
"┌─╮",
"│ │",
"└─╯",
"╭─╴",
"├─ ",
"╰─╴",
"╭─╴",
"├─ ",
"╵ ",
"╭╫╮",
"╰╫╮",
"╰╫╯",
"╭─╮",
"╪═ ",
"┷━╸",
"╭─╮",
"╪═ ",
"╰─╯",
"╭╴ ",
"│ ",
"╰╴ ",
" ╶╮",
" │",
" ╶╯",
];
pub struct Digits {
text: String,
color: Option<Rgb>,
variant: Option<Variant>,
align: Alignment,
bold: bool,
bg: Option<Rgb>,
theme: Option<Theme>,
}
impl Digits {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
color: None,
variant: None,
align: Alignment::Left,
bold: false,
bg: None,
theme: None,
}
}
pub fn width(text: &str) -> u16 {
text.chars().map(|c| if c == ':' { 1 } else { 3 }).sum()
}
pub fn color(mut self, c: Rgb) -> Self {
self.color = Some(c);
self
}
pub fn variant(mut self, v: Variant) -> Self {
self.variant = Some(v);
self
}
pub fn align(mut self, a: Alignment) -> Self {
self.align = a;
self
}
pub fn bold(mut self, b: bool) -> Self {
self.bold = b;
self
}
pub fn bg(mut self, b: Rgb) -> Self {
self.bg = Some(b);
self
}
pub fn theme(mut self, t: &Theme) -> Self {
self.theme = Some(*t);
self
}
}
impl MinSize for Digits {
fn min_size(&self) -> (u16, u16) {
(Self::width(&self.text), 3)
}
}
impl Widget for Digits {
fn render(self, area: Rect, buf: &mut Buffer) {
let th = self.theme.unwrap_or_else(theme::current);
if draw::refuse(buf, area, self.min_size(), th.text_disabled) {
return;
}
let fg = if let Some(c) = self.color {
c
} else if let Some(v) = self.variant {
th.variant(v)
} else {
th.primary
};
let bg = self.bg.unwrap_or(th.surface);
let mut style = st(fg, bg);
if self.bold {
style = style.add_modifier(ratatui_core::style::Modifier::BOLD);
}
let w = Self::width(&self.text);
let start_x = match self.align {
Alignment::Left => area.x,
Alignment::Center => area.x + (area.width.saturating_sub(w)) / 2,
Alignment::Right => area.x + area.width.saturating_sub(w),
};
let mut x = start_x;
for c in self.text.chars() {
if x >= area.x + area.width {
break;
}
let idx = GLYPHS.chars().position(|g| g == c);
let char_w = if c == ':' { 1 } else { 3 };
if let Some(i) = idx {
let base = i * 3;
let lines = [GLYPH_DATA[base], GLYPH_DATA[base + 1], GLYPH_DATA[base + 2]];
for (row, line) in lines.iter().enumerate() {
let y = area.y + row as u16;
if y >= area.y + area.height {
break;
}
put(buf, x, y, line, char_w, style);
}
} else {
let char_rect = Rect {
x,
y: area.y + 1,
width: 3,
height: 1,
};
put_centered(buf, char_rect, &c.to_string(), style);
}
x += char_w;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digit_width() {
assert_eq!(Digits::width("123"), 9);
assert_eq!(Digits::width("12:34"), 13); assert_eq!(Digits::width(":"), 1);
}
#[test]
fn glyph_lookup() {
assert_eq!(GLYPH_DATA[3], "╭─╮"); assert_eq!(GLYPH_DATA[4], "│ │"); assert_eq!(GLYPH_DATA[5], "╰─╯"); }
#[test]
fn draws_at_its_minimum_and_refuses_visibly_below_it() {
fn painted(buf: &Buffer) -> bool {
buf.content().iter().any(|c| c.symbol() != " ")
}
let (w, h) = Digits::new("12:34").min_size();
let mut buf = Buffer::empty(Rect::new(0, 0, w, h));
Digits::new("12:34").render(buf.area, &mut buf);
assert!(painted(&buf), "should draw at its stated minimum");
let mut buf = Buffer::empty(Rect::new(0, 0, w, h.saturating_sub(1)));
Digits::new("12:34").render(buf.area, &mut buf);
assert!(
buf.content().iter().any(|c| c.symbol() == "⋯"),
"one row short must refuse visibly"
);
}
}