use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Widget;
use super::super::theme::Palette;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProgressStyle {
Unicode,
Ascii,
}
pub struct Progress<'a> {
percent: f32,
width: u16,
label: Option<&'a str>,
show_percent: bool,
style: ProgressStyle,
palette: &'a Palette,
}
impl<'a> Progress<'a> {
pub fn new(percent: f32, palette: &'a Palette) -> Self {
Self {
percent: percent.clamp(0.0, 1.0),
width: 20,
label: None,
show_percent: false,
style: ProgressStyle::Unicode,
palette,
}
}
pub fn width(mut self, width: u16) -> Self {
self.width = width.max(1);
self
}
pub fn label(mut self, label: &'a str) -> Self {
self.label = Some(label);
self
}
pub fn show_percent(mut self, show: bool) -> Self {
self.show_percent = show;
self
}
pub fn style(mut self, style: ProgressStyle) -> Self {
self.style = style;
self
}
fn bar_spans(&self) -> Vec<Span<'static>> {
let fill_style = Style::default().fg(self.palette.primary);
let track_style = Style::default().fg(self.palette.dim);
match self.style {
ProgressStyle::Unicode => {
let total_eighths = (self.width as f32) * 8.0;
let filled_eighths = (self.percent * total_eighths).round() as u32;
let full = (filled_eighths / 8) as u16;
let remainder = (filled_eighths % 8) as usize;
let partial = [' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉'][remainder];
let mut spans = vec![];
if full > 0 {
spans.push(Span::styled("█".repeat(full as usize), fill_style));
}
if full < self.width {
if remainder > 0 {
spans.push(Span::styled(partial.to_string(), fill_style));
let empty_cells = self.width - full - 1;
if empty_cells > 0 {
spans.push(Span::styled(" ".repeat(empty_cells as usize), track_style));
}
} else {
let empty_cells = self.width - full;
spans.push(Span::styled(" ".repeat(empty_cells as usize), track_style));
}
}
spans
}
ProgressStyle::Ascii => {
let inner_width = self.width.saturating_sub(2).max(1);
let filled = ((self.percent * inner_width as f32).round() as u16).min(inner_width);
let empty = inner_width - filled;
vec![
Span::styled("[", track_style),
Span::styled("=".repeat(filled as usize), fill_style),
Span::styled(" ".repeat(empty as usize), track_style),
Span::styled("]", track_style),
]
}
}
}
}
impl Widget for Progress<'_> {
fn render(self, area: Rect, buf: &mut Buffer) {
if area.height == 0 || area.width == 0 {
return;
}
let mut spans: Vec<Span> = Vec::new();
if let Some(label) = self.label {
spans.push(Span::styled(
format!("{label} "),
Style::default().fg(self.palette.primary),
));
}
spans.extend(self.bar_spans());
if self.show_percent {
spans.push(Span::styled(
format!(" {:>3}%", (self.percent * 100.0).round() as u32),
Style::default().fg(self.palette.bright),
));
}
Line::from(spans).render(area, buf);
}
}