use std::io::Write;
use crossterm::style::Color;
use crate::common::config::{AppConfig, ThemeConfig};
use crate::ui::text::print_colored_text;
#[allow(dead_code)] pub struct ProgressBar;
#[allow(dead_code)] impl ProgressBar {
pub fn draw<W: Write>(
stdout: &mut W,
label: &str,
value: f64,
max_value: f64,
width: usize,
options: ProgressBarOptions,
) {
let formatted_label = Self::format_label(label);
let available_bar_width = width.saturating_sub(
AppConfig::PROGRESS_BAR_LABEL_WIDTH + AppConfig::PROGRESS_BAR_BRACKET_WIDTH + 1,
);
let fill_ratio = (value / max_value).clamp(0.0, 1.0);
let filled_width = (available_bar_width as f64 * fill_ratio) as usize;
let color = options
.color
.unwrap_or_else(|| ThemeConfig::progress_bar_color(fill_ratio));
let display_text = Self::format_display_text(value, max_value, fill_ratio, &options);
print_colored_text(stdout, &formatted_label, Color::White, None, None);
print_colored_text(stdout, ": [", Color::White, None, None);
Self::draw_bar_content(
stdout,
available_bar_width,
filled_width,
&display_text,
color,
);
print_colored_text(stdout, "]", Color::White, None, None);
}
pub fn draw_simple<W: Write>(
stdout: &mut W,
label: &str,
value: f64,
max_value: f64,
width: usize,
show_text: Option<String>,
) {
let options = ProgressBarOptions {
show_text,
color: None,
style: ProgressBarStyle::Filled,
show_percentage: true,
};
Self::draw(stdout, label, value, max_value, width, options);
}
fn format_label(label: &str) -> String {
if label.len() > AppConfig::PROGRESS_BAR_LABEL_WIDTH {
label[..AppConfig::PROGRESS_BAR_LABEL_WIDTH].to_string()
} else {
format!(
"{label:<width$}",
width = AppConfig::PROGRESS_BAR_LABEL_WIDTH
)
}
}
fn format_display_text(
value: f64,
_max_value: f64,
fill_ratio: f64,
options: &ProgressBarOptions,
) -> String {
if let Some(ref text) = options.show_text {
if text.len() > AppConfig::PROGRESS_BAR_TEXT_WIDTH {
text[..AppConfig::PROGRESS_BAR_TEXT_WIDTH].to_string()
} else {
format!("{text:>width$}", width = AppConfig::PROGRESS_BAR_TEXT_WIDTH)
}
} else if options.show_percentage {
format!("{:>7.1}%", fill_ratio * 100.0)
} else {
format!(
"{value:>width$.1}",
width = AppConfig::PROGRESS_BAR_TEXT_WIDTH
)
}
}
fn draw_bar_content<W: Write>(
stdout: &mut W,
total_width: usize,
filled_width: usize,
display_text: &str,
color: Color,
) {
let text_len = display_text.len();
let text_pos = total_width.saturating_sub(text_len);
for i in 0..total_width {
if i >= text_pos && i < text_pos + text_len {
let char_index = i - text_pos;
if let Some(ch) = display_text.chars().nth(char_index) {
print_colored_text(stdout, &ch.to_string(), Color::White, None, None);
}
} else if i < filled_width {
print_colored_text(stdout, "▬", color, None, None);
} else {
print_colored_text(stdout, "─", Color::DarkGrey, None, None);
}
}
}
}
#[derive(Default)]
#[allow(dead_code)] pub struct ProgressBarOptions {
pub show_text: Option<String>,
pub color: Option<Color>,
pub style: ProgressBarStyle,
pub show_percentage: bool,
}
#[allow(dead_code)] impl ProgressBarOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_text(mut self, text: String) -> Self {
self.show_text = Some(text);
self
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn with_style(mut self, style: ProgressBarStyle) -> Self {
self.style = style;
self
}
pub fn with_percentage(mut self, show_percentage: bool) -> Self {
self.show_percentage = show_percentage;
self
}
}
#[derive(Default)]
#[allow(dead_code)] pub enum ProgressBarStyle {
#[default]
Filled, Blocks, Dots, Lines, }
#[allow(dead_code)] pub struct SpecializedBars;
#[allow(dead_code)] impl SpecializedBars {
pub fn gpu_utilization<W: Write>(stdout: &mut W, utilization: f64, width: usize) {
let options = ProgressBarOptions::new()
.with_color(ThemeConfig::progress_bar_color(utilization / 100.0))
.with_percentage(true);
ProgressBar::draw(stdout, "GPU", utilization, 100.0, width, options);
}
pub fn memory_usage<W: Write>(stdout: &mut W, used_gb: f64, total_gb: f64, width: usize) {
let usage_percent = (used_gb / total_gb) * 100.0;
let options = ProgressBarOptions::new()
.with_text(format!("{used_gb:.1}GB"))
.with_color(ThemeConfig::progress_bar_color(usage_percent / 100.0));
ProgressBar::draw(stdout, "Mem", usage_percent, 100.0, width, options);
}
pub fn temperature<W: Write>(stdout: &mut W, temp_celsius: u32, max_temp: u32, width: usize) {
let temp_ratio = temp_celsius as f64 / max_temp as f64;
let color = if temp_ratio > 0.9 {
Color::Red
} else if temp_ratio > 0.7 {
Color::Yellow
} else {
Color::Green
};
let options = ProgressBarOptions::new()
.with_text(format!("{temp_celsius}°C"))
.with_color(color);
ProgressBar::draw(
stdout,
"Temp",
temp_celsius as f64,
max_temp as f64,
width,
options,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_format_label() {
assert_eq!(ProgressBar::format_label("GPU"), "GPU ");
assert_eq!(ProgressBar::format_label("VeryLongLabel"), "VeryL");
}
#[test]
fn test_progress_bar_options() {
let options = ProgressBarOptions::new()
.with_text("50%".to_string())
.with_color(Color::Green)
.with_percentage(false);
assert_eq!(options.show_text, Some("50%".to_string()));
assert_eq!(options.color, Some(Color::Green));
assert!(!options.show_percentage);
}
#[test]
fn test_specialized_bars() {
let mut buffer = Cursor::new(Vec::new());
SpecializedBars::gpu_utilization(&mut buffer, 75.0, 40);
assert!(!buffer.get_ref().is_empty());
}
}