cli_animate/
interactive_menu.rs

1use std::io::stdin;
2use std::io::{self, Write};
3
4use crate::utils::Color;
5
6pub struct InteractiveMenu {
7    /// Options to be displayed.
8    options: Vec<String>,
9
10    /// The index of the selected option.
11    selected_index: usize,
12
13    /// The style of the interactive menu.
14    style: Style,
15}
16
17/// The style of the interactive menu.
18pub struct Style {
19    color: Color,
20
21    /// The prefix of the selected option, such as `>`, `*`, etc.
22    selected_prefix: char,
23}
24
25impl Default for Style {
26    fn default() -> Self {
27        Self {
28            color: Color::White,
29            selected_prefix: '>',
30        }
31    }
32}
33
34pub struct StyleBuilder {
35    color: Option<Color>,
36    selected_prefix: Option<char>,
37}
38
39impl StyleBuilder {
40    pub fn new() -> StyleBuilder {
41        StyleBuilder {
42            color: None,
43            selected_prefix: None,
44        }
45    }
46
47    pub fn color(mut self, color: Color) -> StyleBuilder {
48        self.color = Some(color);
49        self
50    }
51
52    pub fn selected_prefix(mut self, prefix: char) -> StyleBuilder {
53        self.selected_prefix = Some(prefix);
54        self
55    }
56
57    pub fn build(self) -> Style {
58        Style {
59            color: self.color.unwrap_or(Color::White),
60            selected_prefix: self.selected_prefix.unwrap_or('>'),
61        }
62    }
63}
64
65impl InteractiveMenu {
66    /// `new()` initializes a new interactive menu.
67    pub fn new(options: Vec<String>, style: Style) -> Self {
68        Self {
69            options,
70            selected_index: 0,
71            style,
72        }
73    }
74
75    /// `run()` starts the interactive menu.
76    /// It ends when the user submits an option.
77    pub fn run(&mut self) -> io::Result<usize> {
78        self.display()?;
79
80        loop {
81            let mut input = String::new();
82            stdin().read_line(&mut input)?;
83
84            match input.trim() {
85                "w" | "W" => self.previous(), // 'w' to move up
86                "s" | "S" => self.next(),     // 's' to move down
87                "" => break,                  // Enter key to select
88                _ => continue,
89            }
90
91            self.display()?;
92        }
93
94        Ok(self.selected_index)
95    }
96
97    /// `display()` displays the interactive menu where there are options.
98    fn display(&mut self) -> io::Result<()> {
99        // \x1b[2J clears the screen. x1B[1;1H sets screen size 40 x 25. \x1b[37m sets color as white.
100        println!("\x1B[2J\x1B[1;1H{}", self.style.color.to_ansi_code());
101        io::stdout().flush()?;
102
103        println!("'w' for up, 's' for down, then press Enter to select. Double Enter to submit.\n");
104
105        for (i, option) in self.options.iter().enumerate() {
106            if i == self.selected_index {
107                print!("{} ", self.style.selected_prefix);
108            } else {
109                print!("  ");
110            }
111
112            println!("{}", option);
113        }
114
115        io::stdout().flush()?;
116        Ok(())
117    }
118
119    fn previous(&mut self) {
120        if self.selected_index > 0 {
121            self.selected_index -= 1;
122        } else {
123            self.selected_index = self.options.len() - 1;
124        }
125    }
126
127    fn next(&mut self) {
128        self.selected_index = (self.selected_index + 1) % self.options.len();
129    }
130}