1use better_term::{Color, flush_styles};
2#[cfg(feature="crossterm")]
3use crossterm::{cursor, execute};
4#[cfg(feature="crossterm")]
5use std::io::stdout;
6
7#[derive(Debug, Clone)]
9pub enum BarType {
10 Bar, RawBar, Dots, Line, }
19
20#[derive(Debug, Clone)]
63pub struct PBar {
64 #[cfg(feature="crossterm")]
66 x: u16,
67 #[cfg(feature="crossterm")]
68 y: u16,
69
70 color: bool,
72 bar_type: BarType,
73 show_percent: bool,
74 bar_length: u16,
75
76 repeat_at: u8,
78
79 percent: u8,
81}
82
83impl PBar {
84 #[cfg(not(feature="crossterm"))]
91 pub fn new(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
92 Self {
93 percent: 0,
94 color, bar_type, bar_length, show_percent,
95 repeat_at: 0,
96 }
97 }
98
99 #[cfg(not(feature="crossterm"))]
100 fn set_pos(&mut self) {
101 print!("\r");
102 }
103
104 #[cfg(feature="crossterm")]
111 pub fn new(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
112 Self::new_at(0, 0, bar_type, color, show_percent, bar_length)
113 }
114
115 #[cfg(feature="crossterm")]
124 pub fn new_at(x: u16, y: u16, bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Self {
125 Self {
126 x, y, percent: 0,
127 color, bar_type, bar_length, show_percent,
128 repeat_at: 0,
129 }
130 }
131
132 #[cfg(feature="crossterm")]
139 pub fn new_at_cursor(bar_type: BarType, color: bool, show_percent: bool, bar_length: u16) -> Result<Self, String> {
140 let pos = crossterm::cursor::position();
141 if pos.is_err() {
142 return Err(pos.unwrap_err().to_string());
143 }
144
145 let (x, y) = pos.unwrap();
146
147 Ok(Self::new_at(x, y, bar_type, color, show_percent, bar_length))
148 }
149
150 #[cfg(feature="crossterm")]
151 fn set_pos(&mut self) -> Result<(), String> {
152 let r = execute!(stdout(), cursor::MoveTo(self.x, self.y));
153 if r.is_err() {
154 return Err(r.unwrap_err().to_string());
155 }
156 Ok(())
157 }
158
159 pub fn update(&mut self, mut percent: u8) {
163 if percent > 100 {
164 percent = 100;
165 }
166 self.percent = percent;
167 }
168
169 fn draw_dots_and_line(&mut self, output: String, color: Color) -> String {
170 format!("{}{}", output, if self.show_percent {
171 if self.color {
172 format!(" {}{}%", color, self.percent)
173 } else {
174 format!(" {}%", self.percent)
175 }
176 } else {
177 format!("")
178 })
179 }
180
181 pub fn draw(&mut self) {
183 #[cfg(feature="crossterm")]
184 {
185 let r = self.set_pos();
186 if r.is_err() {
187 panic!("Failed to set cursor position!");
188 }
189 }
190
191 #[cfg(not(feature="crossterm"))]
192 self.set_pos();
193
194 let red = 255 - ((self.percent as f32 / 100.0) * 200.0) as u8;
197 let green = (self.percent as f32 / 100.0 * 200.0) as u8;
198 let color = Color::RGB(red, green, 25);
199
200 let output = match self.bar_type {
201 BarType::RawBar => {
202 let chunk_weight = 100 / self.bar_length;
203 let bar_completion = self.percent as usize / chunk_weight as usize;
205
206 if self.color {
207 let bar = format!("{}{}", color, "█".repeat(bar_completion));
209
210 format!("{}{}", bar, if self.show_percent {
211 format!(" {}{}{}%", color, self.percent, Color::White)
212 } else { format!("") })
213 } else {
214 let bar = format!("{}", "█".repeat(bar_completion));
216
217 format!("{}{}", bar, if self.show_percent {
218 format!(" {}%", self.percent)
219 } else { format!("") })
220 }
221 }
222 BarType::Bar => {
223 let chunk_weight = 100 / self.bar_length;
224 let bar_completion = self.percent as usize / chunk_weight as usize;
226 let mut bar_uncomplete = (100 - (self.percent)) as usize / chunk_weight as usize;
227 let add = bar_completion + bar_uncomplete;
229 if add < self.bar_length as usize {
230 bar_uncomplete += 1;
231 }
232 if add > self.bar_length as usize {
233 bar_uncomplete -= 1;
234 }
235
236 if self.color {
237 let completed_bar = format!("{}{}", color, "█".repeat(bar_completion));
239 let uncompleted_bar = format!("{}{}", Color::BrightBlack, "█".repeat(bar_uncomplete));
240
241 format!("{dc}[{}{}{dc}]{}", completed_bar, uncompleted_bar, if self.show_percent {
243 format!(" {}{}{}%", color, self.percent, Color::White)
244 } else { format!("") }, dc = Color::White)
245 } else {
246 let completed_bar = format!("{}", "█".repeat(bar_completion));
248 let uncompleted_bar = format!("{}", "=".repeat(bar_uncomplete));
249
250 format!("[{}{}]{}", completed_bar, uncompleted_bar, if self.show_percent {
252 format!(" {}%", self.percent)
253 } else { format!("") })
254 }
255 }
256 BarType::Dots => {
257 if self.percent % 2 == 0 {
258 self.repeat_at += 1;
259 if self.repeat_at == 3 {
260 self.repeat_at = 0;
261 }
262 }
263
264 let output = match self.repeat_at {
265 0 => {
266 format!(". ")
267 }
268 1 => {
269 format!(".. ")
270 }
271 _ => {
272 format!("...")
273 }
274 };
275
276 self.draw_dots_and_line(output, color)
277 }
278 BarType::Line => {
279 if self.percent % 2 == 0 {
280 self.repeat_at += 1;
281 if self.repeat_at == 4 {
282 self.repeat_at = 0;
283 }
284 }
285
286 let output = match self.repeat_at {
287 0 => {
288 format!("|")
289 }
290 1 => {
291 format!("/")
292 }
293 2 => {
294 format!("-")
295 }
296 _ => {
297 format!("\\")
298 }
299 };
300
301 self.draw_dots_and_line(output, color)
302 }
303 };
304
305 #[cfg(feature="crossterm")]
306 print!("{}", output);
307 #[cfg(not(feature="crossterm"))]
308 print!("\r{}", output);
309
310 if self.color {
311 flush_styles()
312 }
313 }
314
315 pub fn is_complete(&self) -> bool {
316 self.percent == 100
317 }
318}
319
320#[cfg(feature="crossterm")]
322pub fn hide_cursor() {
323 execute!(stdout(), crossterm::cursor::Hide).expect("Failed to hide cursor!");
324}
325
326#[cfg(feature="crossterm")]
328pub fn show_cursor() {
329 execute!(stdout(), crossterm::cursor::Show).expect("Failed to show cursor!");
330}
331
332impl Default for PBar {
333 #[cfg(feature="crossterm")]
335 fn default() -> Self {
336 Self::new_at_cursor(BarType::Bar, true, true, 20).expect("Failed to make default PBar")
337 }
338
339 #[cfg(not(feature="crossterm"))]
341 fn default() -> Self {
342 Self::new(BarType::Bar, true, true, 20)
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use std::thread;
349 use std::time::Duration;
350 use crossterm::execute;
351 use crossterm::terminal::ClearType;
352 use crate::{BarType, PBar};
353 use crate::{hide_cursor, show_cursor};
354
355 #[test]
356 fn t1() {
357 use std::io::stdout;
358 execute!(stdout(), crossterm::terminal::Clear(ClearType::All)).expect("Failed to clear screen!");
359 let mut pbar = PBar::new_at(0, 1, BarType::RawBar,
360 true, true, 20);
361 let mut pbar2 = PBar::new_at(0, 3, BarType::Bar,
362 true, true, 20);
363 let mut pbar3 = PBar::new_at(7, 5, BarType::Dots,
364 true, true, 20);
365 let mut pbar4 = PBar::new_at(8, 7, BarType::Line,
366 true, true, 20);
367
368 hide_cursor();
369
370 execute!(stdout(), crossterm::cursor::MoveTo(0, 5)).expect("Failed to move!");
371 print!("Loading");
372
373 execute!(stdout(), crossterm::cursor::MoveTo(0, 7)).expect("Failed to move!");
374 print!("Loading");
375
376 let max = 1000;
377 for x in 0..max {
378 let percent = ((x as f32 / (max - 1) as f32) * 100.0) as u8;
379 pbar.update(percent);
380 pbar.draw();
381
382 pbar2.update(percent);
383 pbar2.draw();
384
385 pbar3.update(percent);
386 pbar3.draw();
387
388 pbar4.update(percent);
389 pbar4.draw();
390 thread::sleep(Duration::from_millis(10));
391 }
392 println!();
393 show_cursor();
394 }
395}