use std::io::Write;
use crossterm::{
cursor::MoveTo,
queue,
style::{Color, Print, ResetColor, SetForegroundColor},
};
use crate::layout::centered_column;
use crate::prompt::text_entry;
const BAR_CHAR: char = '\u{2501}';
#[derive(Debug, Clone)]
pub struct TimerBarComponent {
pub progress: f64,
pub visible: bool,
}
impl TimerBarComponent {
pub fn new() -> Self {
Self {
progress: 1.0,
visible: false,
}
}
pub fn hidden() -> Self {
Self::new()
}
pub fn segment_count(progress: f64, tw: u16) -> usize {
let inner = text_entry("", "", tw).inner_width;
visible_segments(inner, progress)
}
#[cfg(not(tarpaulin_include))]
pub fn render<W: Write>(&self, writer: &mut W, tw: u16, row: u16) -> std::io::Result<()> {
if !self.visible {
return Ok(());
}
let seg = Self::segment_count(self.progress, tw);
if seg == 0 {
return Ok(());
}
let col = centered_column(tw, seg as u16);
let body: String = BAR_CHAR.to_string().repeat(seg);
queue!(
writer,
MoveTo(col, row),
SetForegroundColor(Color::DarkGrey),
Print(body),
ResetColor
)
}
}
fn visible_segments(inner: u16, progress: f64) -> usize {
let p = progress.clamp(0.0, 1.0);
let n = ((inner as f64) * p).floor() as usize;
n.min(inner as usize)
}
impl Default for TimerBarComponent {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_is_hidden_with_full_progress() {
let t = TimerBarComponent::new();
assert!(!t.visible);
assert_eq!(t.progress, 1.0);
}
#[test]
fn segment_count_scales_with_progress() {
let tw = 80_u16;
let inner = text_entry("", "", tw).inner_width as usize;
assert_eq!(TimerBarComponent::segment_count(0.0, tw), 0);
assert_eq!(TimerBarComponent::segment_count(1.0, tw), inner);
let half = TimerBarComponent::segment_count(0.5, tw);
assert!(half > 0 && half < inner);
}
#[test]
fn segment_count_clamps_progress() {
let tw = 100_u16;
let inner = text_entry("", "", tw).inner_width as usize;
assert_eq!(TimerBarComponent::segment_count(-1.0, tw), 0);
assert_eq!(TimerBarComponent::segment_count(2.0, tw), inner);
}
#[test]
fn render_skips_when_not_visible() {
let t = TimerBarComponent {
progress: 1.0,
visible: false,
};
let mut buf = Vec::new();
t.render(&mut buf, 80, 5).unwrap();
assert!(buf.is_empty());
}
#[test]
fn render_skips_when_zero_segments() {
let t = TimerBarComponent {
progress: 0.0,
visible: true,
};
let mut buf = Vec::new();
t.render(&mut buf, 80, 5).unwrap();
assert!(buf.is_empty());
}
#[test]
fn render_writes_bar_when_visible() {
let n = TimerBarComponent::segment_count(1.0, 80);
let t = TimerBarComponent {
progress: 1.0,
visible: true,
};
let mut buf = Vec::new();
t.render(&mut buf, 80, 3).unwrap();
let s = String::from_utf8(buf).unwrap();
assert_eq!(s.matches(BAR_CHAR).count(), n);
}
}