counter_cli/
loading_bar.rs1use std::io::{self, Write};
2use crossterm::{QueueableCommand, cursor, ExecutableCommand};
3
4pub struct LoadingBar {
5 total: u32,
6 current: u32,
7 width: u16,
8 avg: Option<f32>,
9 buffer: String,
10}
11
12impl LoadingBar {
13 pub fn new(total: u32) -> Self {
14 Self {
15 total,
16 current: 0,
17 width: ((crossterm::terminal::size().unwrap_or((2, 0)).0 as f32 - 2.0) * 0.56) as u16, avg: None,
19 buffer: " ".repeat(crossterm::terminal::size().unwrap_or((2, 0)).0 as usize -2),
20 }
21 }
22
23 #[inline]
24 pub fn update(&mut self, elapsed_s: f32, increment: u32) -> &mut Self {
25 let clamped = increment.clamp(0, self.total - self.current);
26 self.current += clamped;
27 self.avg = match self.avg {
28 Some(avg) => Some((avg + clamped as f32 / elapsed_s) / 2.0),
29 None => Some(clamped as f32 / elapsed_s),
30 }; self
31 }
32
33 #[inline]
35 pub fn draw(&mut self, description: &str) -> bool {
36 let mut stdout = io::stdout();
37
38 let mul = self.current as f32 / self.total as f32; let percent = mul * 100.0;
40 let eta = (self.total - self.current) as f32 / self.avg.unwrap_or(1.0);
41
42 let loaded = "#".repeat((mul * self.width as f32) as usize);
43 let unloaded = " ".repeat(self.width as usize - loaded.len());
44
45 print!("{}\n{}{}\n{}{}\n{}", self.buffer, cursor::MoveToColumn(0), self.buffer, cursor::MoveToColumn(0), self.buffer, cursor::MoveToColumn(0));
47 stdout
48 .queue(cursor::Hide).unwrap()
49 .queue(cursor::MoveUp(3)).unwrap()
50 .queue(cursor::MoveToColumn(0)).unwrap();
51
52 write!(
54 stdout,
55 "\x1b[36;1minfo: \x1b[0m{description}...\n{}\x1b[34m> [\x1b[32m{loaded}{unloaded}\x1b[34m]\n{}> | \x1b[33m{percent:.2}% \x1b[34m| \x1b[33m{}\x1b[34m/\x1b[33m{} \x1b[34m|\x1b[0m{}",
56 cursor::MoveToColumn(0),
57 cursor::MoveToColumn(0),
58 self.current,
59 self.total,
60 match self.avg {
61 Some(avg) => format!(" \x1b[33m{} \x1b[34m| \x1b[36meta\x1b[34m: \x1b[33m{} \x1b[34m|\x1b[0m", Self::right_avg_unit(avg), Self::right_time_unit(eta)),
62 None => String::new(),
63 },
64 ).unwrap();
65 let _ = stdout.flush(); if self.current == self.total {
69 let _ = stdout.execute(cursor::Show);
70 return true;
71 }
72
73 stdout .queue(cursor::MoveUp(2)).unwrap()
75 .queue(cursor::MoveToColumn(0)).unwrap();
76 false
77 }
78
79 #[inline]
81 pub fn right_time_unit(seconds: f32) -> String {
82 if seconds < 60.0 {
83 format!("{:.2}s", seconds)
84 } else if seconds < 3600.0 {
85 format!("{:.2}min", seconds / 60.0)
86 } else {
87 format!("{:.2}h", seconds / 3600.0)
88 }
89 }
90
91 #[inline]
93 pub fn right_avg_unit(avg: f32) -> String {
94 if avg < 1.0 {
95 format!("{}/i", Self::right_time_unit(1.0 / avg))
96 } else {
97 format!("{:.2}i/s", avg)
98 }
99 }
100}