use core::fmt;
use retroglyph_core::{Color, Rect, Style};
use unicode_width::UnicodeWidthStr;
use super::{Meter, Text, Widget};
use crate::Surface;
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 default_label_style() -> Style {
Style::new().fg(Color::Rgb {
r: 180,
g: 180,
b: 200,
})
}
pub(super) fn render(
surface: &mut Surface<'_>,
area: Rect,
label: &str,
label_style: Style,
ratio: f32,
readout: &str,
) {
if area.width() < 4 {
return;
}
let ratio = ratio.clamp(0.0, 1.0);
let color = Meter::new(ratio).color();
let y = area.top();
let label_w = label.width().min(area.width_usize());
let reserved = label_w + 1 + readout.width() + 1; let bar_w = area.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(), y, label_w_u16, 1);
Text::new(label)
.style(label_style)
.render(label_area, surface);
let mut x = area.left() + label_w_u16 + 1;
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
let filled = (ratio * bar_w as f32).round() as usize;
let filled_style = Style::new().fg(color);
let empty_style = Style::new().fg(Color::Rgb {
r: 50,
g: 50,
b: 60,
});
for i in 0..bar_w {
let (ch, style) = if i < filled {
('█', filled_style)
} else {
('░', empty_style)
};
surface.put((x, y), ch, style);
x += 1;
}
x += 1; let readout_area = Rect::new(x, y, area.right().saturating_sub(x), 1);
Text::new(readout)
.style(Style::new().fg(color))
.render(readout_area, surface);
}
#[cfg(test)]
mod tests {
use core::fmt::Write as _;
use retroglyph_core::{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),
area,
"あ",
default_label_style(),
0.5,
"",
);
assert_eq!(grid[Pos::new(3, 0)].glyph(), '█');
}
}