#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProgressConfig {
pub min: f64,
pub max: f64,
pub show_spinner: bool,
pub bar_width_px: u32,
}
#[derive(Debug, Clone)]
pub struct ProgressState {
pub config: ProgressConfig,
pub current: f64,
pub label: String,
pub done: bool,
pub spinner_tick: u32,
}
pub fn default_progress_config() -> ProgressConfig {
ProgressConfig {
min: 0.0,
max: 1.0,
show_spinner: true,
bar_width_px: 200,
}
}
pub fn new_progress_indicator(config: ProgressConfig) -> ProgressState {
ProgressState {
config,
current: 0.0,
label: String::new(),
done: false,
spinner_tick: 0,
}
}
pub fn progress_set(state: &mut ProgressState, value: f64) {
state.current = value.clamp(state.config.min, state.config.max);
}
pub fn progress_advance(state: &mut ProgressState, delta: f64) {
let new_val = (state.current + delta).min(state.config.max);
state.current = new_val.max(state.config.min);
state.spinner_tick = state.spinner_tick.wrapping_add(1);
}
pub fn progress_complete(state: &mut ProgressState) {
state.current = state.config.max;
state.done = true;
}
pub fn progress_is_done(state: &ProgressState) -> bool {
state.done
}
pub fn progress_fraction(state: &ProgressState) -> f64 {
let range = state.config.max - state.config.min;
if range <= 0.0 {
return 1.0;
}
((state.current - state.config.min) / range).clamp(0.0, 1.0)
}
pub fn progress_label(state: &ProgressState) -> &str {
&state.label
}
pub fn progress_to_json(state: &ProgressState) -> String {
format!(
"{{\"current\":{:.6},\"min\":{:.6},\"max\":{:.6},\
\"fraction\":{:.6},\"done\":{},\"label\":\"{}\",\"spinner_tick\":{}}}",
state.current,
state.config.min,
state.config.max,
progress_fraction(state),
state.done,
state.label,
state.spinner_tick,
)
}
pub fn progress_reset(state: &mut ProgressState) {
state.current = state.config.min;
state.done = false;
state.label.clear();
state.spinner_tick = 0;
}
#[cfg(test)]
mod tests {
use super::*;
fn default_state() -> ProgressState {
new_progress_indicator(default_progress_config())
}
#[test]
fn default_config_values() {
let cfg = default_progress_config();
assert!((cfg.min - 0.0).abs() < 1e-10);
assert!((cfg.max - 1.0).abs() < 1e-10);
assert!(cfg.show_spinner);
}
#[test]
fn new_state_at_zero() {
let s = default_state();
assert!((progress_fraction(&s) - 0.0).abs() < 1e-10);
assert!(!progress_is_done(&s));
}
#[test]
fn set_progress_clamps_to_max() {
let mut s = default_state();
progress_set(&mut s, 2.0);
assert!((s.current - 1.0).abs() < 1e-10);
}
#[test]
fn set_progress_half() {
let mut s = default_state();
progress_set(&mut s, 0.5);
assert!((progress_fraction(&s) - 0.5).abs() < 1e-10);
}
#[test]
fn advance_increments_progress() {
let mut s = default_state();
progress_advance(&mut s, 0.3);
assert!((progress_fraction(&s) - 0.3).abs() < 1e-10);
}
#[test]
fn advance_does_not_exceed_max() {
let mut s = default_state();
progress_advance(&mut s, 0.8);
progress_advance(&mut s, 0.8);
assert!((progress_fraction(&s) - 1.0).abs() < 1e-10);
}
#[test]
fn complete_marks_done() {
let mut s = default_state();
progress_complete(&mut s);
assert!(progress_is_done(&s));
assert!((progress_fraction(&s) - 1.0).abs() < 1e-10);
}
#[test]
fn reset_clears_state() {
let mut s = default_state();
progress_set(&mut s, 0.7);
progress_complete(&mut s);
progress_reset(&mut s);
assert!(!progress_is_done(&s));
assert!((progress_fraction(&s) - 0.0).abs() < 1e-10);
}
#[test]
fn to_json_contains_fraction_and_done() {
let mut s = default_state();
progress_set(&mut s, 0.5);
let json = progress_to_json(&s);
assert!(json.contains("\"fraction\""));
assert!(json.contains("\"done\""));
assert!(json.contains("\"current\""));
}
#[test]
fn label_accessor() {
let mut s = default_state();
s.label = "Processing...".to_string();
assert_eq!(progress_label(&s), "Processing...");
}
}