use crate::style::style::ProgressStyle;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct BarState {
pub pos: u64,
pub len: Option<u64>,
pub message: String,
pub prefix: String,
pub postfix: String,
pub started_at: Instant,
pub last_draw_pos: u64,
pub last_draw_time: Instant,
pub draw_delta: u64,
pub draw_rate: u64,
pub finished: bool,
pub abandoned: bool,
pub hidden: bool,
pub style: ProgressStyle,
pub steady_tick_interval: Option<Duration>,
pub spinner_frame_index: usize,
}
impl BarState {
pub fn new(len: Option<u64>, style: ProgressStyle) -> BarState {
let now = Instant::now();
BarState {
pos: 0,
len,
message: String::new(),
prefix: String::new(),
postfix: String::new(),
started_at: now,
last_draw_pos: 0,
last_draw_time: now,
draw_delta: 1,
draw_rate: 15,
finished: false,
abandoned: false,
hidden: false,
style,
steady_tick_interval: None,
spinner_frame_index: 0,
}
}
pub fn should_redraw(&self) -> bool {
if self.hidden {
return false;
}
let moved = self.pos.saturating_sub(self.last_draw_pos);
if self.draw_delta == 0 || moved >= self.draw_delta {
return true;
}
if self.draw_rate == 0 {
return true;
}
let min_interval = Duration::from_secs_f64(1.0 / self.draw_rate as f64);
self.last_draw_time.elapsed() >= min_interval
}
pub fn update_draw_time(&mut self) {
self.last_draw_pos = self.pos;
self.last_draw_time = Instant::now();
}
pub fn fraction(&self) -> f64 {
match self.len {
Some(0) | None => 0.0,
Some(len) => (self.pos as f64 / len as f64).clamp(0.0, 1.0),
}
}
pub fn eta(&self) -> Duration {
let Some(len) = self.len else {
return Duration::ZERO;
};
if self.pos == 0 || self.pos >= len {
return Duration::ZERO;
}
let rate = self.per_sec();
if rate <= 0.0 {
Duration::ZERO
} else {
Duration::from_secs_f64((len - self.pos) as f64 / rate)
}
}
pub fn per_sec(&self) -> f64 {
let elapsed = self.started_at.elapsed().as_secs_f64();
if elapsed <= 0.0 {
0.0
} else {
self.pos as f64 / elapsed
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fraction_zero_len() {
let state = BarState::new(Some(0), ProgressStyle::default_bar());
assert_eq!(state.fraction(), 0.0);
}
#[test]
fn test_fraction_halfway() {
let mut state = BarState::new(Some(10), ProgressStyle::default_bar());
state.pos = 5;
assert_eq!(state.fraction(), 0.5);
}
#[test]
fn test_fraction_over_clamps() {
let mut state = BarState::new(Some(10), ProgressStyle::default_bar());
state.pos = 20;
assert_eq!(state.fraction(), 1.0);
}
#[test]
fn test_should_redraw_by_delta() {
let mut state = BarState::new(Some(10), ProgressStyle::default_bar());
state.draw_delta = 3;
state.pos = 3;
assert!(state.should_redraw());
}
#[test]
fn test_eta_estimate() {
let mut state = BarState::new(Some(20), ProgressStyle::default_bar());
state.pos = 10;
state.started_at = Instant::now() - Duration::from_secs(10);
let eta = state.eta();
assert!(eta.as_secs() >= 9 && eta.as_secs() <= 11);
}
}