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("━".repeat(empty_width), Style::new().foreground(Color::Ansi256(237)))); }
94 spans
95 }
96}
97
98#[derive(Debug)]
100pub struct PercentageColumn(pub Style);
101
102impl Default for PercentageColumn {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl PercentageColumn {
109 pub fn new() -> Self {
110 Self(Style::new().foreground(Color::Cyan))
111 }
112}
113
114impl ProgressColumn for PercentageColumn {
115 fn render(&self, task: &Task) -> Vec<Span> {
116 let percentage = task.percentage() * 100.0;
117 vec![Span::styled(format!("{:>3.0}%", percentage), self.0)]
118 }
119}
120
121#[derive(Debug)]
123pub struct SpinnerColumn {
124 spinner: Spinner, }
126
127impl Default for SpinnerColumn {
128 fn default() -> Self {
129 Self::new()
130 }
131}
132
133impl SpinnerColumn {
134 pub fn new() -> Self {
135 Self {
136 spinner: Spinner::new("").style(SpinnerStyle::Dots),
137 }
138 }
139}
140
141impl ProgressColumn for SpinnerColumn {
142 fn render(&self, task: &Task) -> Vec<Span> {
143 let style = self.spinner.get_style();
154 let interval = style.interval_ms();
155 let frames = style.frames();
156 let elapsed_ms = task.elapsed().as_millis() as u64;
157 let idx = ((elapsed_ms / interval) as usize) % frames.len();
158
159 vec![Span::styled(frames[idx].to_string(), Style::new().foreground(Color::Green))]
160 }
161}
162
163#[derive(Debug)]
165pub struct TransferSpeedColumn;
166
167impl ProgressColumn for TransferSpeedColumn {
168 fn render(&self, task: &Task) -> Vec<Span> {
169 let speed = task.speed();
170 let speed_str = if speed >= 1_000_000.0 {
171 format!("{:.1} MB/s", speed / 1_000_000.0)
172 } else if speed >= 1_000.0 {
173 format!("{:.1} KB/s", speed / 1_000.0)
174 } else {
175 format!("{:.0} B/s", speed)
176 };
177 vec![Span::styled(speed_str, Style::new().foreground(Color::Red))]
178 }
179}
180
181#[derive(Debug)]
183pub struct TimeRemainingColumn;
184
185impl ProgressColumn for TimeRemainingColumn {
186 fn render(&self, task: &Task) -> Vec<Span> {
187 let eta = match task.eta() {
188 Some(d) => format_duration(d),
189 None => "-:--:--".to_string(),
190 };
191 vec![Span::styled(eta, Style::new().foreground(Color::Cyan))]
192 }
193}
194
195fn format_duration(d: Duration) -> String {
196 let secs = d.as_secs();
197 if secs >= 3600 {
198 format!("{:02}:{:02}:{:02}", secs / 3600, (secs % 3600) / 60, secs % 60)
199 } else {
200 format!("{:02}:{:02}", secs / 60, secs % 60)
201 }
202}
203
204#[derive(Debug)]
205pub struct MofNColumn {
206 separator: String
207}
208
209impl Default for MofNColumn {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl MofNColumn {
216 pub fn new() -> Self {
217 Self { separator: "/".to_string() }
218 }
219}
220
221impl ProgressColumn for MofNColumn {
222 fn render(&self, task: &Task) -> Vec<Span> {
223 let completed = task.completed;
224 let total = task.total.unwrap_or(0);
225 vec![Span::styled(format!("{}{}{}", completed, self.separator, total), Style::new().foreground(Color::Green))]
226 }
227}
228
229#[derive(Debug)]
230pub struct ElapsedColumn;
231
232impl ProgressColumn for ElapsedColumn {
233 fn render(&self, task: &Task) -> Vec<Span> {
234 let elapsed = task.elapsed();
235 vec![Span::styled(format_duration(elapsed), Style::new().foreground(Color::Cyan))]
236 }
237}