use core::fmt;
use retroglyph_core::color::{Color, Style};
use retroglyph_core::grid::Rect;
use retroglyph_core::text::width_usize as measured_width;
use super::{Meter, Text, Widget};
use crate::Surface;
pub(super) fn meter_fill_color(ratio: f32) -> Color {
Meter::new(ratio).color()
}
const EMPTY_BAR_COLOR: Color = Color::rgb(50, 50, 60);
pub(super) struct ReadoutBuf<const N: usize> {
bytes: [u8; N],
len: usize,
}
impl<const N: usize> ReadoutBuf<N> {
pub(super) const fn new() -> Self {
Self {
bytes: [0; N],
len: 0,
}
}
pub(super) fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len]).unwrap_or("")
}
}
impl<const N: usize> fmt::Write for ReadoutBuf<N> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let bytes = s.as_bytes();
let end = self.len + bytes.len();
if end > N {
return Err(fmt::Error);
}
self.bytes[self.len..end].copy_from_slice(bytes);
self.len = end;
Ok(())
}
}
pub(super) fn render(
surface: &mut Surface<'_>,
label: &str,
label_style: Style,
ratio: f32,
readout: &str,
fill_color: fn(f32) -> Color,
) {
let width = surface.width();
if width < 4 {
return;
}
let ratio = ratio.clamp(0.0, 1.0);
let color = fill_color(ratio);
let area = surface.area();
let width_usize = usize::from(width);
let label_w = measured_width(label).min(width_usize);
let reserved = label_w + 1 + measured_width(readout) + 1; let bar_w = width_usize.saturating_sub(reserved);
#[allow(clippy::cast_possible_truncation)]
let label_w_u16 = label_w as u16;
let label_area = Rect::new(area.left(), area.top(), label_w_u16, 1);
Text::new(label)
.style(label_style)
.render(&mut surface.scope(label_area));
let mut x = label_w_u16 + 1;
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
let filled = retroglyph_core::math::round(ratio * bar_w as f32) as usize;
let filled_style = Style::new().fg(color);
let empty_style = Style::new().fg(EMPTY_BAR_COLOR);
for i in 0..bar_w {
let (ch, style) = if i < filled {
('█', filled_style)
} else {
('░', empty_style)
};
surface.put((x, 0), ch, style);
x += 1;
}
x += 1; let readout_area = Rect::new(area.left() + x, area.top(), width.saturating_sub(x), 1);
Text::new(readout)
.style(Style::new().fg(color))
.render(&mut surface.scope(readout_area));
}
#[cfg(test)]
mod tests {
use core::fmt::Write as _;
use retroglyph_core::grid::{Grid, Pos};
use super::*;
use crate::Surface;
#[test]
fn readout_buf_formats_without_allocating() {
let mut buf = ReadoutBuf::<4>::new();
write!(buf, "{:>3}%", 87).unwrap();
assert_eq!(buf.as_str(), " 87%");
}
#[test]
fn readout_buf_rejects_writes_past_capacity_and_keeps_what_fit() {
let mut buf = ReadoutBuf::<4>::new();
assert!(write!(buf, "12345").is_err());
assert_eq!(buf.as_str(), "");
}
#[test]
fn wide_char_label_uses_display_width_not_byte_length() {
let area = Rect::new(0, 0, 20, 1);
let mut grid = Grid::new(20, 1);
render(
&mut Surface::new(&mut grid, area, 0),
"あ",
Style::new(),
0.5,
"",
meter_fill_color,
);
assert_eq!(grid[Pos::new(3, 0)].glyph(), '█');
}
}