fast_rich/progress/
columns.rs1use crate::progress::Task;
2use crate::progress::spinner::{Spinner, SpinnerStyle};
4use crate::style::{Color, Style};
5use crate::text::Span;
6use std::fmt::Debug;
7use std::time::Duration;
8
9pub trait ProgressColumn: Send + Sync + Debug {
11 fn render(&self, task: &Task) -> Vec<Span>;
13}
14
15#[derive(Debug)]
17pub struct TextColumn {
18 text: String,
19 style: Style,
20}
21
22impl TextColumn {
23 pub fn new(text: &str) -> Self {
24 Self {
25 text: text.to_string(),
26 style: Style::new(),
27 }
28 }
29
30 pub fn styled(text: &str, style: Style) -> Self {
31 Self {
32 text: text.to_string(),
33 style,
34 }
35 }
36}
37
38impl ProgressColumn for TextColumn {
39 fn render(&self, task: &Task) -> Vec<Span> {
40 let text = if self.text == "[progress.description]" {
42 &task.description
43 } else {
44 &self.text
45 };
46
47 vec![Span::styled(text.clone(), self.style)]
48 }
49}
50
51#[derive(Debug)]
53pub struct BarColumn {
54 pub bar_width: usize,
55 pub complete_style: Style,
56 pub finished_style: Option<Style>,
57 pub pulse_style: Option<Style>,
58}
59
60impl BarColumn {
61 pub fn new(bar_width: usize) -> Self {
62 Self {
63 bar_width,
64 complete_style: Style::new().foreground(Color::Magenta), finished_style: Some(Style::new().foreground(Color::Green)),
66 pulse_style: None,
67 }
68 }
69}
70
71impl ProgressColumn for BarColumn {
72 fn render(&self, task: &Task) -> Vec<Span> {
73 let total = task.total.unwrap_or(100) as f64;
74 let completed = task.completed as f64;
75 let percentage = (completed / total).clamp(0.0, 1.0);
76
77 let width = self.bar_width;
78 let filled_width = (width as f64 * percentage).round() as usize;
79 let empty_width = width.saturating_sub(filled_width);
80
81 let style = if task.finished {
82 self.finished_style.unwrap_or(self.complete_style)
83 } else {
84 self.complete_style
85 };
86
87 let mut spans = Vec::new();
88 if filled_width > 0 {
89 spans.push(Span::styled("━".repeat(filled_width), style));
90 }
91 if empty_width > 0 {
92 spans.push(Span::styled(
93 "━".repeat(empty_width),
94 Style::new().foreground(Color::Ansi256(237)),
95 )); }
97 spans
98 }
99}
100
101#[derive(Debug)]
103pub struct PercentageColumn(pub Style);
104
105impl Default for PercentageColumn {
106 fn default() -> Self {
107 Self::new()
108 }
109}
110
111impl PercentageColumn {
112 pub fn new() -> Self {
113 Self(Style::new().foreground(Color::Cyan))
114 }
115}
116
117impl ProgressColumn for PercentageColumn {
118 fn render(&self, task: &Task) -> Vec<Span> {
119 let percentage = task.percentage() * 100.0;
120 vec![Span::styled(format!("{:>3.0}%", percentage), self.0)]
121 }
122}
123
124#[derive(Debug)]
126pub struct SpinnerColumn {
127 spinner: Spinner, }
129
130impl Default for SpinnerColumn {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl SpinnerColumn {
137 pub fn new() -> Self {
138 Self {
139 spinner: Spinner::new("").style(SpinnerStyle::Dots),
140 }
141 }
142}
143
144impl ProgressColumn for SpinnerColumn {
145 fn render(&self, task: &Task) -> Vec<Span> {
146 let style = self.spinner.get_style();
157 let interval = style.interval_ms();
158 let frames = style.frames();
159 let elapsed_ms = task.elapsed().as_millis() as u64;
160 let idx = ((elapsed_ms / interval) as usize) % frames.len();
161
162 vec![Span::styled(
163 frames[idx].to_string(),
164 Style::new().foreground(Color::Green),
165 )]
166 }
167}
168
169#[derive(Debug)]
171pub struct TransferSpeedColumn;
172
173impl ProgressColumn for TransferSpeedColumn {
174 fn render(&self, task: &Task) -> Vec<Span> {
175 let speed = task.speed();
176 let speed_str = if speed >= 1_000_000.0 {
177 format!("{:.1} MB/s", speed / 1_000_000.0)
178 } else if speed >= 1_000.0 {
179 format!("{:.1} KB/s", speed / 1_000.0)
180 } else {
181 format!("{:.0} B/s", speed)
182 };
183 vec![Span::styled(speed_str, Style::new().foreground(Color::Red))]
184 }
185}
186
187#[derive(Debug)]
189pub struct TimeRemainingColumn;
190
191impl ProgressColumn for TimeRemainingColumn {
192 fn render(&self, task: &Task) -> Vec<Span> {
193 let eta = match task.eta() {
194 Some(d) => format_duration(d),
195 None => "-:--:--".to_string(),
196 };
197 vec![Span::styled(eta, Style::new().foreground(Color::Cyan))]
198 }
199}
200
201fn format_duration(d: Duration) -> String {
202 let secs = d.as_secs();
203 if secs >= 3600 {
204 format!(
205 "{:02}:{:02}:{:02}",
206 secs / 3600,
207 (secs % 3600) / 60,
208 secs % 60
209 )
210 } else {
211 format!("{:02}:{:02}", secs / 60, secs % 60)
212 }
213}
214
215#[derive(Debug)]
216pub struct MofNColumn {
217 separator: String,
218}
219
220impl Default for MofNColumn {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226impl MofNColumn {
227 pub fn new() -> Self {
228 Self {
229 separator: "/".to_string(),
230 }
231 }
232}
233
234impl ProgressColumn for MofNColumn {
235 fn render(&self, task: &Task) -> Vec<Span> {
236 let completed = task.completed;
237 let total = task.total.unwrap_or(0);
238 vec![Span::styled(
239 format!("{}{}{}", completed, self.separator, total),
240 Style::new().foreground(Color::Green),
241 )]
242 }
243}
244
245#[derive(Debug)]
246pub struct ElapsedColumn;
247
248impl ProgressColumn for ElapsedColumn {
249 fn render(&self, task: &Task) -> Vec<Span> {
250 let elapsed = task.elapsed();
251 vec![Span::styled(
252 format_duration(elapsed),
253 Style::new().foreground(Color::Cyan),
254 )]
255 }
256}