loading_bar/
text_loading_bar.rs

1//! Home of the TextLoadingBar methods and its associated structs and enums.
2
3use std::{
4    collections::HashMap,
5    fmt::{self, Display},
6    io::stdout,
7};
8
9use crate::loading_bar::LoadingBar;
10pub use auto_run::{TextLoadingBarAutoOptions, TextLoadingBarAutoPoint};
11pub use colored::Color;
12
13use colored::Colorize;
14use crossterm::{
15    cursor::{MoveTo, RestorePosition, SavePosition},
16    execute,
17    style::Print,
18    terminal::{Clear, ClearType},
19};
20
21pub enum TextLoadingBarOptions {
22    Text(String),
23    Color(Option<Color>),
24    Number(u16),
25    Float(f32),
26    Pos(u16, u16),
27    None,
28}
29
30impl TextLoadingBarOptions {
31    fn get_text(&self) -> &str {
32        match self {
33            TextLoadingBarOptions::Text(text) => text,
34            _ => "",
35        }
36    }
37
38    fn get_color(&self) -> Option<Color> {
39        match self {
40            TextLoadingBarOptions::Color(color) => *color,
41            _ => None,
42        }
43    }
44
45    fn get_number(&self) -> u16 {
46        match self {
47            TextLoadingBarOptions::Number(number) => *number,
48            _ => 0,
49        }
50    }
51
52    fn get_float(&self) -> f32 {
53        match self {
54            TextLoadingBarOptions::Float(float) => *float,
55            _ => 0.0,
56        }
57    }
58
59    fn get_pos(&self) -> (u16, u16) {
60        match self {
61            TextLoadingBarOptions::Pos(x, y) => (*x, *y),
62            _ => (0, 0),
63        }
64    }
65}
66
67// we have 2 structs for the main (TextLoadingbar) struct, so its easier to manage the elements such as (TextItem) and what color they should be etc
68// for each of these structs we have to have a position, for crossterm::cursor to be able to use and
69// we also have to have a color, so we can use the color struct to set the color of the text (which we might have to change the type from Colored::color to crossterm::style::Color)
70#[derive(Debug)]
71struct TextItem {
72    text: String,
73    color: Option<colored::Color>,
74}
75
76// in the main struct we have a two text items, one for above the bar and one for below the bar
77// we also have a loading bar item, which is the bar itself
78// we also have the largest text item, which is the text that is the largest in the bar (used for centering the text)
79#[derive(Debug)]
80pub struct TextLoadingBar {
81    top_text: TextItem,
82    bottom_text: TextItem,
83    t_bar: LoadingBar,
84    t_start_pos: (u16, u16),
85}
86
87impl Display for TextLoadingBar {
88    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89        write!(
90            f,
91            "{}\n{}\n{}",
92            self.top_text
93                .text
94                .color(self.top_text.color.unwrap_or(colored::Color::White)),
95            self.t_bar,
96            self.bottom_text
97                .text
98                .color(self.bottom_text.color.unwrap_or(colored::Color::White)) // if we have a color, use it, otherwise use white
99        )
100    }
101}
102
103impl TextLoadingBar {
104    pub fn new(
105        top_text: String,
106        bottom_text: String,
107        len: u16,
108        color: (
109            Option<colored::Color>, // top text color
110            Option<colored::Color>, // bar color
111            Option<colored::Color>, // bottom text color
112        ),
113        t_start_pos: (u16, u16),
114    ) -> TextLoadingBar {
115        let (top_text_color, bar_color, bottom_text_color) = color;
116        TextLoadingBar {
117            top_text: TextItem {
118                text: top_text,
119                color: top_text_color,
120            },
121            t_bar: LoadingBar::new(len, bar_color, (0, 0)),
122            bottom_text: TextItem {
123                text: bottom_text,
124                color: bottom_text_color,
125            },
126            t_start_pos,
127        }
128    }
129
130    fn goline_clear_print(&self) {
131        let line_count = get_num_lines_witdh(&self.to_string());
132
133        let (x, y) = self.t_start_pos;
134        let mut y_copy = y;
135        execute!(stdout(), SavePosition).expect("\x07failed to save position\x07");
136        for _ in 0..line_count {
137            execute!(stdout(), MoveTo(x, y_copy)).expect("\x07failed to move cursor\x07");
138            execute!(stdout(), Clear(ClearType::UntilNewLine)).expect("\x07failed to clear\x07");
139            execute!(stdout(), RestorePosition).expect("\x07failed to restore cursor\x07");
140            y_copy += 1;
141        }
142
143        let text = self.to_string();
144        let text = text.split('\n').collect::<Vec<&str>>();
145
146        y_copy = y;
147        for i in 0..line_count {
148            let texts = &text[i as usize];
149            execute!(stdout(), MoveTo(x, y_copy)).expect("\x07failed to move cursor\x07");
150            execute!(stdout(), Print(texts)).expect("\x07failed to print\x07");
151            execute!(stdout(), RestorePosition).expect("\x07failed to restore cursor\x07");
152            y_copy += 1;
153        }
154    }
155
156    pub fn print(&self) {
157        self.goline_clear_print();
158    }
159
160    pub fn change_pos(&mut self, t_start_pos: (u16, u16)) {
161        // first we have to clear the text
162        let line_count = get_num_lines_witdh(&self.to_string());
163
164        let (x, y) = self.t_start_pos;
165        let mut y_copy = y;
166        execute!(stdout(), SavePosition).expect("\x07failed to save position\x07");
167        for _ in 0..line_count {
168            execute!(stdout(), MoveTo(x, y_copy)).expect("\x07failed to move cursor\x07");
169            execute!(stdout(), Clear(ClearType::UntilNewLine)).expect("\x07failed to clear\x07");
170            execute!(stdout(), RestorePosition).expect("\x07failed to restore cursor\x07");
171            y_copy += 1;
172        }
173
174        self.t_start_pos = t_start_pos;
175    }
176
177    pub fn change_pos_print(&mut self, t_start_pos: (u16, u16)) {
178        self.change_pos(t_start_pos);
179        self.print();
180    }
181
182    pub fn change_top_text(&mut self, text: String) {
183        self.top_text.text = text;
184    }
185
186    pub fn change_bottom_text(&mut self, text: String) {
187        self.bottom_text.text = text;
188    }
189
190    pub fn change_top_text_color(&mut self, color: Option<Color>) {
191        self.top_text.color = color;
192    }
193
194    pub fn change_bottom_text_color(&mut self, color: Option<Color>) {
195        self.bottom_text.color = color;
196    }
197
198    pub fn change_bar_color(&mut self, color: Option<Color>) {
199        self.t_bar.color = color;
200    }
201
202    pub fn change_all_text_colors(&mut self, color: Option<Color>) {
203        self.top_text.color = color;
204        self.bottom_text.color = color;
205        self.t_bar.color = color;
206    }
207
208    pub fn advance(&mut self) {
209        self.t_bar.advance();
210    }
211
212    pub fn advance_print(&mut self) {
213        self.advance();
214        self.goline_clear_print();
215    }
216
217    pub fn advance_by(&mut self, index: u16) {
218        self.t_bar.advance_by(index);
219    }
220
221    pub fn advance_by_print(&mut self, index: u16) {
222        self.advance_by(index);
223        self.goline_clear_print();
224    }
225
226    pub fn advance_by_percent(&mut self, percent: f32) {
227        self.t_bar.advance_by_percent(percent);
228    }
229
230    pub fn advance_by_percent_print(&mut self, percent: f32) {
231        self.advance_by_percent(percent);
232        self.goline_clear_print();
233    }
234
235    // func change this function takes a hashmap
236    pub fn change(&mut self, map: HashMap<&str, TextLoadingBarOptions>, print: bool) {
237        for (key, value) in map {
238            match key {
239                "top_text" => {
240                    self.change_top_text(value.get_text().to_string());
241                }
242                "bottom_text" => {
243                    self.change_bottom_text(value.get_text().to_string());
244                }
245                "top_text_color" => {
246                    self.change_top_text_color(value.get_color());
247                }
248                "bottom_text_color" => {
249                    self.change_bottom_text_color(value.get_color());
250                }
251                "bar_color" => {
252                    self.change_bar_color(value.get_color());
253                }
254                "all_text_colors" => {
255                    self.change_all_text_colors(value.get_color());
256                }
257                "advance" => {
258                    self.advance();
259                }
260                "advance_by" => {
261                    self.advance_by(value.get_number());
262                }
263                "advance_by_percent" => {
264                    self.advance_by_percent(value.get_float());
265                }
266                "change_pos" => {
267                    self.change_pos(value.get_pos());
268                }
269                _ => {
270                    panic!("\x07{}\x07 is not a valid key", key);
271                }
272            }
273        }
274        if print {
275            self.print();
276        }
277    }
278}
279
280fn get_num_lines_witdh(text: &str) -> u16 {
281    let mut num_lines = 1;
282    for c in text.chars() {
283        if c == '\n' {
284            num_lines += 1;
285        }
286    }
287    num_lines
288}
289
290mod auto_run {
291    use std::{
292        collections::HashMap,
293        fmt::{self, Display},
294        thread,
295        time::Duration,
296    };
297
298    use colored::Color;
299
300    use crate::{text_loading_bar::TextLoadingBar, Types};
301    #[derive(Debug)]
302    pub struct TextLoadingBarAutoOptions {
303        pub top_text: Vec<String>,
304        pub bottom_text: Vec<String>,
305        pub top: Vec<Option<Color>>,
306        pub bottom: Vec<Option<Color>>,
307        pub bar: Vec<Option<Color>>,
308    }
309
310    pub struct TextLoadingBarAutoPoint<T> {
311        pub top_text: HashMap<T, String>,
312        pub bottom_text: HashMap<T, String>,
313        pub top: HashMap<T, Option<Color>>,
314        pub bottom: HashMap<T, Option<Color>>,
315        pub bar: HashMap<T, Option<Color>>,
316    }
317    enum TextLoadingBarAutoOptionsType {
318        TopText,
319        BottomText,
320        Top,
321        Bottom,
322        Bar,
323    }
324
325    impl Display for TextLoadingBarAutoOptionsType {
326        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327            match self {
328                TextLoadingBarAutoOptionsType::TopText => write!(f, "top_text"),
329                TextLoadingBarAutoOptionsType::BottomText => write!(f, "bottom_text"),
330                TextLoadingBarAutoOptionsType::Top => write!(f, "top"),
331                TextLoadingBarAutoOptionsType::Bottom => write!(f, "bottom"),
332                TextLoadingBarAutoOptionsType::Bar => write!(f, "bar"),
333            }
334        }
335    }
336
337    impl TextLoadingBarAutoOptions {
338        pub fn get_len(&self) -> (u16, u16, u16, u16, u16) {
339            (
340                TextLoadingBarAutoOptions::check(
341                    self.top_text.len(),
342                    TextLoadingBarAutoOptionsType::TopText,
343                ),
344                TextLoadingBarAutoOptions::check(
345                    self.bottom_text.len(),
346                    TextLoadingBarAutoOptionsType::BottomText,
347                ),
348                TextLoadingBarAutoOptions::check(
349                    self.top.len(),
350                    TextLoadingBarAutoOptionsType::Top,
351                ),
352                TextLoadingBarAutoOptions::check(
353                    self.bottom.len(),
354                    TextLoadingBarAutoOptionsType::Bottom,
355                ),
356                TextLoadingBarAutoOptions::check(
357                    self.bar.len(),
358                    TextLoadingBarAutoOptionsType::Bar,
359                ),
360            )
361        }
362        fn check(num: usize, types: TextLoadingBarAutoOptionsType) -> u16 {
363            if num == 0 {
364                panic!("{} is 0", types);
365            } else {
366                num as u16
367            }
368        }
369    }
370    impl TextLoadingBar {
371        pub fn auto_run(
372            top_text: String,
373            bottom_text: String,
374            time_in_seconds: u16,
375            len: u16,
376            start: u16,
377            color: (
378                Option<colored::Color>, // top text color
379                Option<colored::Color>, // bar color
380                Option<colored::Color>, // bottom text color
381            ),
382            t_start_pos: (u16, u16),
383        ) {
384            if start >= len {
385                println!();
386                panic!("\x07start must be less than len\x07");
387            }
388            let (top_text_color, bar_color, bottom_text_color) = color;
389            let self_clone = TextLoadingBar::new(
390                top_text,
391                bottom_text,
392                len,
393                (top_text_color, bar_color, bottom_text_color),
394                t_start_pos,
395            );
396            TextLoadingBar::auto_run_from(self_clone, time_in_seconds)
397        }
398
399        pub fn auto_run_change(
400            option: TextLoadingBarAutoOptions,
401            time_in_seconds: u16,
402            len: u16,
403            start: u16,
404            start_pos: (u16, u16),
405        ) {
406            if start >= len {
407                println!();
408                panic!("\x07start must be less than len\x07");
409            }
410            let mut self_clone = TextLoadingBar::new(
411                option.top_text[0].clone(),
412                option.bottom_text[0].clone(),
413                len,
414                (option.top[0], option.bar[0], option.bottom[0]),
415                start_pos,
416            );
417            self_clone.advance_by(start);
418            TextLoadingBar::auto_run_from_change(self_clone, option, time_in_seconds)
419        }
420
421        // might have to use default fields for texts and colors
422        pub fn auto_run_change_points<T>(
423            time_in_seconds: u16,
424            len: u16,
425            start: u16,
426            start_pos: (u16, u16),
427            change: TextLoadingBarAutoPoint<T>,
428            type_change: Types,
429        ) where
430            T: Copy + fmt::Debug,
431            u16: From<T>,
432            f32: From<T>,
433        {
434            // TODO: implement
435            if start >= len {
436                println!();
437                panic!("\x07start must be less than len\x07");
438            }
439            let mut self_clone = TextLoadingBar::new(
440                "".to_string(),
441                "".to_string(),
442                len,
443                (None, None, None),
444                start_pos,
445            );
446            self_clone.advance_by(start);
447            TextLoadingBar::auto_run_from_change_points(
448                self_clone,
449                change,
450                time_in_seconds,
451                type_change,
452            )
453        }
454        pub fn auto_run_from(mut text_loading_bar: TextLoadingBar, time_in_seconds: u16) {
455            let index = time_in_seconds as f32 / (text_loading_bar.t_bar.space_left + 1) as f32;
456
457            text_loading_bar.print();
458            thread::spawn(move || {
459                for _ in 0..(text_loading_bar.t_bar.space_left) {
460                    text_loading_bar.advance_print();
461                    thread::sleep(Duration::from_secs_f32(index));
462                }
463            });
464        }
465
466        pub fn auto_run_from_change(
467            text_loading_bar: TextLoadingBar,
468            option: TextLoadingBarAutoOptions,
469            time_in_seconds: u16,
470        ) {
471            let (top_len, bottom_len, bar_color_len, top_color_len, bottom_color_len) =
472                option.get_len();
473            // find the the bar index(s) for each variable we just took from the option struct
474
475            let change = TextLoadingBarAutoPoint {
476                bottom: crate::get_index_and_value(
477                    bottom_color_len,
478                    text_loading_bar.t_bar.space_left + 1,
479                    text_loading_bar.t_bar.len,
480                    &option.bottom,
481                ),
482                top: crate::get_index_and_value(
483                    top_color_len,
484                    text_loading_bar.t_bar.space_left + 1,
485                    text_loading_bar.t_bar.len,
486                    &option.top,
487                ),
488                top_text: crate::get_index_and_value(
489                    top_len,
490                    text_loading_bar.t_bar.space_left + 1,
491                    text_loading_bar.t_bar.len,
492                    &option.top_text,
493                ),
494                bottom_text: crate::get_index_and_value(
495                    bottom_len,
496                    text_loading_bar.t_bar.space_left + 1,
497                    text_loading_bar.t_bar.len,
498                    &option.bottom_text,
499                ),
500                bar: crate::get_index_and_value(
501                    bar_color_len,
502                    text_loading_bar.t_bar.space_left + 1,
503                    text_loading_bar.t_bar.len,
504                    &option.bar,
505                ),
506            };
507            TextLoadingBar::auto_run_from_change_points(
508                text_loading_bar,
509                change,
510                time_in_seconds,
511                Types::Index,
512            )
513        }
514
515        pub fn auto_run_from_change_points<T>(
516            mut loading_bar: TextLoadingBar,
517            change: TextLoadingBarAutoPoint<T>,
518            time_in_seconds: u16,
519            type_change: Types,
520        ) where
521            T: Copy + fmt::Debug,
522            u16: From<T>,
523            f32: From<T>,
524        {
525            // TODO: implement this
526            let index = time_in_seconds as f32 / (loading_bar.t_bar.space_left + 1) as f32;
527            let mut total = loading_bar.t_bar.len - (loading_bar.t_bar.space_left);
528            let top = crate::generic_to_u16(loading_bar.t_bar.len, change.top_text, type_change);
529            let bottom =
530                crate::generic_to_u16(loading_bar.t_bar.len, change.bottom_text, type_change);
531            let bar_color = crate::generic_to_u16(loading_bar.t_bar.len, change.bar, type_change);
532            let top_color = crate::generic_to_u16(loading_bar.t_bar.len, change.top, type_change);
533            let bottom_color =
534                crate::generic_to_u16(loading_bar.t_bar.len, change.bottom, type_change);
535            loading_bar.print();
536            thread::spawn(move || {
537                for _ in 0..(loading_bar.t_bar.space_left) {
538                    total += 1;
539                    if top.contains_key(&total) {
540                        loading_bar.top_text.text = top[&total].clone();
541                    }
542                    if bottom.contains_key(&total) {
543                        loading_bar.bottom_text.text = bottom[&total].clone();
544                    }
545                    if bar_color.contains_key(&total) {
546                        loading_bar.t_bar.color = bar_color[&total];
547                    }
548                    if top_color.contains_key(&total) {
549                        loading_bar.top_text.color = top_color[&total];
550                    }
551                    if bottom_color.contains_key(&total) {
552                        loading_bar.bottom_text.color = bottom_color[&total];
553                    }
554
555                    loading_bar.advance();
556                    thread::sleep(Duration::from_secs_f32(index));
557                    loading_bar.print()
558                }
559            });
560        }
561    }
562}
563
564mod change_at {
565    use crate::text_loading_bar::TextLoadingBar;
566    impl TextLoadingBar {
567        // TODO: implement change at type functions
568    }
569}