progressed/progress/
progress_bar.rs

1use std::{io::stdout, time::Instant};
2
3use crossterm::ExecutableCommand;
4
5use crate::{
6    defaults::DEFAULT_WIDTH,
7    style::{ProgressBarLayout, ProgressBarStyle},
8    symbols::blocks::get_block_by_progress,
9};
10
11/// Draws a progress bar while iterating over a finite range.
12///
13/// # Example Usage
14/// ```no_run
15///   use progressed::ProgressBar;
16///
17///   let bar = ProgressBar::new(0..100).set_title("Running Job: ");
18///   for _ in bar {
19///       // prints progress bar
20///   }
21///
22/// ```
23pub struct ProgressBar<I: ExactSizeIterator> {
24    data: I,
25    current_index: usize,
26    max_len: usize,
27    width: usize,
28    title: String,
29    start_time: Instant,
30    style: ProgressBarStyle,
31    start_pos: (u16, u16),
32}
33
34impl<I: ExactSizeIterator> ProgressBar<I> {
35    pub fn new(iter: I) -> Self {
36        let len = iter.len();
37        let width = if let Ok((w, _)) = crossterm::terminal::size() {
38            (w / 2) as usize
39        } else {
40            DEFAULT_WIDTH
41        };
42
43        let start_pos = crossterm::cursor::position().unwrap_or((0, 0));
44        Self {
45            data: iter,
46            current_index: 0,
47            max_len: len,
48            width,
49            title: String::new(),
50            start_time: Instant::now(),
51            style: ProgressBarStyle::default(),
52            start_pos,
53        }
54    }
55}
56
57fn draw_counter(surround: (char, char), index: usize, len: usize, show: bool) -> String {
58    if !show {
59        return String::new();
60    }
61
62    let surround_left = surround.0;
63    let surround_right = surround.1;
64    let max_str_width = len.to_string().len();
65
66    format!(" {surround_left}{index:max_str_width$}/{len}{surround_right} ")
67}
68
69fn draw_percentage(progress: f64, show: bool) -> String {
70    if !show {
71        return String::new();
72    }
73
74    format!(" {percent:3.0}% ", percent = progress * 100.0)
75}
76
77fn draw_time(start_time: Instant, show: bool) -> String {
78    if !show {
79        return String::new();
80    }
81
82    let duration = start_time.elapsed();
83    let seconds = duration.as_secs() % 60;
84    let minutes = (duration.as_secs() / 60) % 60;
85    let hours = (duration.as_secs() / 60) / 60;
86
87    format!(" {:02}:{:02}:{:02} ", hours, minutes, seconds)
88}
89
90impl<I: ExactSizeIterator> Iterator for ProgressBar<I> {
91    type Item = I::Item;
92    fn next(&mut self) -> Option<Self::Item> {
93        let progress = self.current_index as f64 / self.max_len as f64;
94        let str_len = (progress * self.width as f64) as usize;
95
96        let counter = draw_counter(
97            self.style.get_counter_surround(),
98            self.current_index,
99            self.max_len,
100            self.style.get_show_counter(),
101        );
102
103        let percentage = draw_percentage(progress, self.style.get_show_percentage());
104        let time = draw_time(self.start_time, self.style.get_show_time());
105
106        let title = &self.title;
107        let surround_left = self.style.get_bar_surround().0;
108        let surround_right = self.style.get_bar_surround().1;
109        let fg_symbol = self.style.get_fg_symbol().to_string();
110        let bg_symbol = self.style.get_bg_symbol().to_string();
111        let fg = vec![fg_symbol.clone(); str_len].join("");
112        let bg = vec![bg_symbol; self.width - str_len].join("");
113
114        let tip = if str_len != self.width && str_len != 0 {
115            self.style.get_tip_symbol().to_string()
116        } else {
117            fg_symbol
118        };
119
120        let tip = if self.style.get_is_smooth() {
121            if str_len != self.width {}
122            let tip_decimal = (progress * self.width as f64) % 1.0;
123
124            format!("{}", get_block_by_progress(tip_decimal))
125        } else {
126            tip
127        };
128
129        if stdout().execute(crossterm::cursor::Hide).is_ok() {}
130
131        let (x, y) = self.start_pos;
132        if let Ok(_) = stdout().execute(crossterm::cursor::MoveTo(x, y)) {}
133
134        match self.style.get_layout() {
135            ProgressBarLayout::AllRight => {
136                print!("{title}{surround_left}{fg}{tip}{bg}{surround_right}{counter}{percentage}{time}");
137            }
138            ProgressBarLayout::AllLeft => {
139                print!("{title}{counter}{percentage}{time}{surround_left}{fg}{tip}{bg}{surround_right}");
140            }
141            ProgressBarLayout::TimerRight => {
142                print!("{title}{counter}{percentage}{surround_left}{fg}{tip}{bg}{surround_right}{time}");
143            }
144            ProgressBarLayout::CounterRight => {
145                print!("{title}{percentage}{time}{surround_left}{fg}{tip}{bg}{surround_right}{counter}");
146            }
147            ProgressBarLayout::PercentageRight => {
148                print!("{title}{counter}{time}{surround_left}{fg}{tip}{bg}{surround_right}{percentage}");
149            }
150            ProgressBarLayout::TimerAndCounterRight => {
151                print!("{title}{percentage}{surround_left}{fg}{tip}{bg}{surround_right}{counter}{time}");
152            }
153            ProgressBarLayout::TimerAndPercentageRight => {
154                print!("{title}{counter}{surround_left}{fg}{tip}{bg}{surround_right}{percentage}{time}");
155            }
156            ProgressBarLayout::CounterAndPercentageRight => {
157                print!("{title}{time}{surround_left}{fg}{tip}{bg}{surround_right}{counter}{percentage}");
158            }
159        }
160
161        if str_len == self.width {
162            print!("\n");
163            if stdout().execute(crossterm::cursor::Show).is_ok() {}
164        }
165        self.current_index += 1;
166        self.data.next()
167    }
168}
169
170impl<I: ExactSizeIterator> ProgressBar<I> {
171    pub fn set_width(mut self, width: usize) -> Self {
172        self.width = width;
173        self
174    }
175
176    pub fn set_style(mut self, style: ProgressBarStyle) -> Self {
177        self.style = style;
178        self
179    }
180
181    pub fn set_title(mut self, title: &str) -> Self {
182        self.title = title.to_owned();
183        self
184    }
185}