term_kit/
progressbar.rs

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}