1use crossterm::{
2 cursor, execute,
3 style::{Color, Print, ResetColor, SetForegroundColor},
4 terminal::{size, Clear, ClearType},
5};
6use std::io::{stdout, Write};
7
8pub struct ProgressBar {
9 value: u16,
10 max: u16,
11 width: u16,
12 label: String,
13}
14
15impl ProgressBar {
16 pub fn new(label: &str, max: u16, width: u16) -> Self {
17 Self {
18 value: 0,
19 max,
20 width,
21 label: label.to_string(),
22 }
23 }
24
25 pub fn update(&mut self, value: u16) {
26 self.value = value.min(self.max);
27 }
28
29 pub fn render(&self) -> Result<(), Box<dyn std::error::Error>> {
30 let percent = (self.value as f64 / self.max as f64) * 100.0;
31 let filled_width = (percent / 100.0 * self.width as f64).round() as u16;
32 let mut stdout = stdout();
33
34 execute!(stdout, cursor::MoveToColumn(0))?;
35 execute!(stdout, Clear(ClearType::UntilNewLine))?;
36 execute!(stdout, Print(format!("{: <20}", self.label)))?;
37 execute!(stdout, Print("["))?;
38 execute!(stdout, SetForegroundColor(Color::Green))?;
39
40 for _ in 0..filled_width {
41 execute!(stdout, Print("■"))?;
42 }
43
44 execute!(stdout, ResetColor)?;
45 for _ in filled_width..self.width {
46 execute!(stdout, Print("-"))?;
47 }
48
49 execute!(stdout, Print("] "), Print(format!("{:.1}%", percent)),)?;
50
51 stdout.flush()?;
52 Ok(())
53 }
54}
55
56pub struct ProgressBarManager {
57 bars: Vec<ProgressBar>,
58}
59
60impl ProgressBarManager {
61 pub fn new() -> Self {
62 Self { bars: Vec::new() }
63 }
64
65 pub fn add_bar(&mut self, label: &str, max: u16, width: u16) {
66 self.bars.push(ProgressBar::new(label, max, width));
67 }
68
69 pub fn update_bar(&mut self, index: usize, value: u16) {
70 if let Some(bar) = self.bars.get_mut(index) {
71 bar.update(value);
72 }
73 }
74
75 pub fn render_all(&self) -> Result<(), Box<dyn std::error::Error>> {
76 let mut stdout = stdout();
77
78 let (cursor_x, cursor_y) = cursor::position()?;
79 let (_, terminal_height) = size()?;
80 let space_below = terminal_height.saturating_sub(cursor_y + 1);
81 let start_row = if space_below >= self.bars.len() as u16 {
82 cursor_y + 1
83 } else {
84 cursor_y.saturating_sub(self.bars.len() as u16)
85 };
86
87 for (i, bar) in self.bars.iter().enumerate() {
88 execute!(stdout, cursor::MoveTo(0, start_row + i as u16))?;
89 bar.render()?;
90 }
91
92 execute!(stdout, cursor::MoveTo(cursor_x, cursor_y))?;
93
94 stdout.flush()?;
95 Ok(())
96 }
97}