1use std::thread::sleep;
4use std::time::Duration;
5use std::io::{stdout, Write, stderr};
6
7const ANIMATION_STEPS_PER_SECOND: u64 = 4;
9const ANIMATION_SLEEP_TIMEOUT: u64 = 1000 / ANIMATION_STEPS_PER_SECOND;
11
12static ANIMATION_STATES: [char; 4] = ['—', '\\', '|', '/'];
14
15#[derive(Debug)]
17pub enum Destination {
18 STDOUT,
20 STDERR,
22}
23
24#[derive(Debug)]
26pub enum ProgressFnKind {
27 PERCENT,
29 PROGRESS
31}
32
33pub fn show_loading_animation(from: usize,
38 to: usize,
39 target: Destination,
40 progress_fn: &dyn Fn() -> usize,
41 fn_kind: ProgressFnKind) {
42 assert!(from <= to);
43
44 let mut animation_step = 0;
45 loop {
46 let (curr_percent, curr_progress) = get_curr_prog_n_perc(progress_fn, &fn_kind, from, to);
47
48 let indicator = ANIMATION_STATES[animation_step];
50 print_line(indicator, curr_progress, to, curr_percent, &target);
51
52 if curr_progress >= to { break; }
54
55 animation_step = (animation_step + 1) % ANIMATION_STATES.len();
57
58 sleep(Duration::from_millis(ANIMATION_SLEEP_TIMEOUT));
59 }
60}
61
62
63#[no_mangle]
73fn show_loading_animation_ffi(from: usize,
75 to: usize,
76 target: Destination,
77 progress_in_percentage_fn: extern "C" fn() -> usize,
78 fn_kind: ProgressFnKind) {
79 let fn_closure = || { progress_in_percentage_fn() };
80 show_loading_animation(from, to, target, &fn_closure, fn_kind);
81}
82
83fn get_curr_prog_n_perc(fnc: &dyn Fn() -> usize,
86 fn_kind: &ProgressFnKind,
87 from: usize,
88 to: usize) -> (usize, usize) {
89 let mut percentage: usize;
90 let mut total_current_progress: usize;
91 if let ProgressFnKind::PERCENT = fn_kind {
92 percentage = fnc();
93 if percentage > 100 {
94 percentage = 100;
95 }
96 total_current_progress = calc_progress(from, to, percentage);
97 } else {
98 total_current_progress = fnc();
99 if total_current_progress > to {
100 total_current_progress = to;
101 }
102 percentage = calc_percentage(from, to, total_current_progress);
103 }
104 (percentage, total_current_progress)
105}
106
107fn print_line(indicator: char, curr_progress: usize, to: usize, curr_percent: usize, target: &Destination) {
109 let msg = format!("\r [{}] {}/{} ({}%)", indicator, curr_progress, to, curr_percent);
111
112 if let Destination::STDOUT = &target {
113 stdout().write_all(msg.as_bytes()).unwrap();
114 stdout().flush().unwrap();
116 } else {
117 stderr().write_all(msg.as_bytes()).unwrap();
118 stderr().flush().unwrap();
120 }
121}
122
123fn calc_progress(from: usize, to: usize, percent: usize) -> usize {
125 assert!(percent <= 100);
127 let normalized = (to - from) as f64;
128 let progress = (percent as f64) / 100_f64;
129 let progress = (progress * normalized) as usize;
130 let progress = progress + from;
131 progress
132}
133
134fn calc_percentage(from: usize, to: usize, progress: usize) -> usize {
136 assert!(from <= progress);
137 assert!(progress <= to);
138 let normalized_to = to - from;
139 if normalized_to == 0 { return 100 }
140
141 let normalized_to = normalized_to as f64;
142 let normalized_progress = (progress - from) as f64;
143 ((normalized_progress / normalized_to) * 100_f64) as usize
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_calc_progress() {
152 assert_eq!(0, calc_progress(0, 100, 0));
153 assert_eq!(5, calc_progress(0, 100, 5));
154 assert_eq!(100, calc_progress(0, 100, 100));
155
156 assert_eq!(0 + 10, calc_progress(10, 20, 0));
157 assert_eq!(1 + 10, calc_progress(10, 20, 10));
158 assert_eq!(10 + 10, calc_progress(10, 20, 100));
159
160 assert_eq!(0, calc_progress(0, 0, 0));
161 }
162
163 #[test]
164 #[should_panic]
165 fn test_calc_progress_panic_1() {
166 calc_progress(0, 100, 101);
167 }
168
169 #[test]
170 fn test_calc_percentage() {
171 assert_eq!(0, calc_percentage(0, 100, 0));
172 assert_eq!(20, calc_percentage(0, 100, 20));
173 assert_eq!(100, calc_percentage(0, 100, 100));
174
175 assert_eq!(0, calc_percentage(10, 20, 10));
176 assert_eq!(50, calc_percentage(10, 20, 15));
177 assert_eq!(100, calc_percentage(10, 20, 20));
178
179 assert_eq!(100, calc_percentage(0, 0, 0));
181 }
182
183 #[test]
184 #[should_panic]
185 fn test_calc_percentage_panic_1() {
186 calc_percentage(20, 100, 19);
187 }
188
189 #[test]
190 #[should_panic]
191 fn test_calc_percentage_panic_2() {
192 calc_percentage(20, 100, 101);
193 }
194}