1use super::styles;
2use crate::{BarExt, std::Bar, term::Colorizer};
3use std::num::{NonZeroI16, NonZeroU16};
4
5#[cfg(feature = "spinner")]
6use crate::spinner::Spinner;
7
8#[derive(Debug, Clone)]
10pub enum Column {
11 Animation,
15 Count,
17 CountTotal,
19 ElapsedTime,
21 Percentage(usize),
23 Rate,
25 RemainingTime,
27 #[cfg(feature = "spinner")]
29 #[cfg_attr(docsrs, doc(cfg(feature = "spinner")))]
30 Spinner(Spinner),
31 Text(String),
42 Total,
44}
45
46#[derive(BarExt, Debug)]
65pub struct RichProgress {
66 pub columns: Vec<Column>,
67 #[bar]
68 pub pb: Bar,
69}
70
71impl RichProgress {
72 pub fn new(pb: Bar, columns: Vec<Column>) -> Self {
74 Self { columns, pb }
75 }
76
77 pub fn replace(&mut self, index: usize, col: Column) {
83 *self.columns.get_mut(index).unwrap() = col;
84 }
86
87 pub fn render(&mut self) -> String {
89 let mut bar_text = vec![];
90 let mut bar_length = 0;
91 let mut progress_bar_index = None;
92
93 for col in self.columns.iter() {
94 match col {
95 Column::Animation => {
96 progress_bar_index = Some(bar_text.len());
97 bar_text.push(String::new());
98 }
99
100 Column::Count => {
101 let fmt_progress = self.pb.fmt_counter();
102 bar_length += fmt_progress.len();
103 bar_text.push(fmt_progress.colorize("green"));
104 }
105
106 Column::CountTotal => {
107 let fmt_progress = format!("{}/{}", self.pb.fmt_counter(), self.pb.fmt_total());
108 bar_length += fmt_progress.len();
109 bar_text.push(fmt_progress.colorize("green"));
110 }
111
112 Column::ElapsedTime => {
113 let elapsed_time = self.pb.fmt_elapsed_time();
114 bar_length += elapsed_time.len();
115 bar_text.push(elapsed_time.colorize("cyan"));
116 }
117
118 Column::Percentage(precision) => {
119 let percentage = format!("{:.1$}%", self.pb.percentage() * 100., precision);
120 bar_length += percentage.len();
121 bar_text.push(percentage.colorize("magenta"));
122 }
123
124 Column::Rate => {
125 let rate = self.pb.fmt_rate();
126 bar_length += rate.len();
127 bar_text.push(rate.colorize("red"));
128 }
129
130 Column::RemainingTime => {
131 let remaining_time = self.pb.fmt_remaining_time();
132 bar_length += remaining_time.len();
133 bar_text.push(remaining_time.colorize("cyan"));
134 }
135
136 #[cfg(feature = "spinner")]
137 Column::Spinner(spinner) => {
138 let frame = spinner.render_frame(self.pb.elapsed_time());
139 bar_length += frame.chars().count();
140 bar_text.push(frame.colorize("green"));
141 }
142
143 Column::Text(text) => {
144 let (color, text_start_index) = match (text.find('['), text.find(']')) {
145 (Some(start), Some(end)) => {
146 if start == 0 {
147 (text.get(1..end), end + 1)
148 } else {
149 (None, 0)
150 }
151 }
152 _ => (None, 0),
153 };
154
155 if let Some(code) = color {
156 let text = &text[text_start_index..];
157 bar_length += text.len_ansi();
158 bar_text.push(text.colorize(code));
159 } else {
160 bar_length += text.len_ansi();
161 bar_text.push(text.to_owned());
162 }
163 }
164
165 Column::Total => {
166 let fmt_progress = self.pb.fmt_total();
167 bar_length += fmt_progress.len();
168 bar_text.push(fmt_progress.colorize("green"));
169 }
170 }
171 }
172
173 bar_length += bar_text.len() - 1;
174 let mut ncols = 0;
175
176 if let Some(progress_bar_index) = progress_bar_index {
177 ncols = self.pb.ncols_for_animation(bar_length as u16);
178
179 if ncols == 0 {
180 let _ = bar_text.remove(progress_bar_index);
181 } else {
182 *bar_text.get_mut(progress_bar_index).unwrap() =
183 if self.pb.indefinite() || !self.pb.started() {
184 styles::pulse(
185 NonZeroI16::new(ncols as i16).unwrap(),
186 self.pb.elapsed_time(),
187 )
188 } else {
189 styles::bar(NonZeroU16::new(ncols).unwrap(), self.pb.percentage())
190 };
191 }
192 }
193
194 self.pb.bar_length = ncols + bar_length as u16;
195 bar_text.join(" ")
196 }
197}