progressed/progress/
progress_bar.rs1use 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
11pub 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}