gut_lib/
display.rs

1use crossterm::{
2  cursor::{Hide, MoveLeft, MoveRight, MoveUp, Show},
3  event::{self, Event, KeyCode, KeyEvent},
4  execute,
5  style::{Color, Print, ResetColor, SetForegroundColor},
6  terminal::{disable_raw_mode, enable_raw_mode},
7};
8use unicode_segmentation::UnicodeSegmentation;
9
10use std::io::stdout;
11use std::{thread, time};
12
13/// Displays a selection list in the terminal
14///
15/// The function will use the provided String array and list the items in the terminal.
16/// The user can then make a selection from the list using the arrow keys.
17/// The index of the provided array is returned as the selection.
18///
19/// This function will panic if it fails to write to the terminal.
20pub fn select_from_list(list: &[String], selected: Option<usize>) -> usize {
21  let mut selection = selected.unwrap_or(0);
22
23  // get selection color
24  let color = get_color();
25
26  // hide cursor
27  execute!(stdout(), Hide).expect("Failed to write hide cursor");
28
29  // enable raw mode
30  enable_raw_mode().expect("Failed to enable raw mode");
31
32  loop {
33    // write list
34    write_list(&list, selection, color);
35
36    // read user input
37    if let Event::Key(KeyEvent { code, .. }) = event::read().expect("Failed to read event") {
38      match code {
39        KeyCode::Esc => {
40          // show cursor
41          execute!(stdout(), Show).expect("Failed to write show cursor");
42          // disable raw mode
43          disable_raw_mode().expect("Failed to enable raw mode");
44          // exit application
45          std::process::exit(0)
46        }
47        KeyCode::Enter => break,
48        KeyCode::Up => {
49          if selection > 0 {
50            selection -= 1;
51          }
52        }
53        KeyCode::Down => {
54          if selection < (list.len() - 1) {
55            selection += 1;
56          }
57        }
58        _ => {}
59      }
60    }
61
62    // clear list
63    clear_list(&list);
64
65    thread::sleep(time::Duration::from_millis(50));
66  }
67
68  // show cursor
69  execute!(stdout(), Show).expect("Failed to write show cursor");
70  // disable raw mode
71  disable_raw_mode().expect("Failed to enable raw mode");
72
73  selection
74}
75
76/// Displays a two column text in the terminal
77///
78/// The function will use the provided Strings and output them in two columns.
79///
80/// This function will panic if it fails to write to the terminal.
81pub fn write_column(first: String, second: String, offset: Option<usize>) {
82  // calculate offset
83  let offset = offset.unwrap_or(20);
84  let diff = offset - first.graphemes(true).count();
85
86  execute!(stdout(), MoveLeft(1), Print(first)).expect("Failed to write first column");
87  execute!(
88    stdout(),
89    MoveRight(diff as u16),
90    Print(format!("{}\n\r", second))
91  )
92  .expect("Failed to write second column");
93}
94
95/// Displays the provided String array and current selection
96fn write_list(list: &[String], selection: usize, color: Color) {
97  // print the list out
98  for (i, item) in list.iter().enumerate() {
99    if i == selection {
100      execute!(
101        stdout(),
102        SetForegroundColor(color),
103        Print(format!("{}\n\r", item)),
104        ResetColor
105      )
106      .expect("Failed to write list");
107    } else {
108      execute!(stdout(), ResetColor, Print(format!("{}\n\r", item))).expect("Failed to write list");
109    }
110  }
111
112  // reset color
113  execute!(stdout(), ResetColor).expect("Failed to write reset color");
114}
115
116/// Moves the terminal cursor back up to be able re-display the list
117fn clear_list(list: &[String]) {
118  // move cursor up
119  for _ in list {
120    execute!(stdout(), MoveUp(1)).expect("Failed to write cursor up");
121  }
122}
123
124fn get_color() -> Color {
125  let conf = crate::config::get_gut_config();
126
127  let selected = match conf.get("color") {
128    Some(color) => color.to_string(),
129    None => "Red".to_string(),
130  };
131
132  if selected.contains("Black") {
133    return Color::Black;
134  } else if selected.contains("Red") {
135    return Color::Red;
136  } else if selected.contains("Green") {
137    return Color::Green;
138  } else if selected.contains("Yellow") {
139    return Color::Yellow;
140  } else if selected.contains("Blue") {
141    return Color::Blue;
142  } else if selected.contains("Magenta") {
143    return Color::Magenta;
144  } else if selected.contains("Cyan") {
145    return Color::Cyan;
146  } else if selected.contains("White") {
147    return Color::White;
148  } else {
149    return Color::Red;
150  }
151}