terminal_menu/lib.rs
1//! Display simple menus on the terminal!
2//! [Examples](https://gitlab.com/xamn/terminal-menu-rs/tree/master/examples)
3
4mod fancy_menu;
5mod utils;
6
7use std::sync::{Arc, RwLock, RwLockWriteGuard};
8use std::thread;
9use std::time::Duration;
10use crossterm::style::Color;
11
12pub type TerminalMenu = Arc<RwLock<TerminalMenuStruct>>;
13
14enum TMIKind {
15 Label,
16 Button,
17 BackButton,
18 Scroll { values: Vec<String>, selected: usize },
19 List { values: Vec<String>, selected: usize },
20 String { value: String, allow_empty: bool },
21 Password { value: String, allow_empty: bool },
22 Numeric { value: f64, step: Option<f64>, min: Option<f64>, max: Option<f64> },
23 Submenu(TerminalMenu),
24}
25pub struct TerminalMenuItem {
26 name: String,
27 kind: TMIKind,
28 color: crossterm::style::Color,
29}
30
31
32
33/// Make a label terminal-menu item.
34/// Can't be selected.
35/// Useful for example as a title, separator, or help text.
36/// # Example
37/// ```
38/// use terminal_menu::{menu, label, list};
39/// let menu = menu(vec![
40/// label("This is my menu:"),
41/// list("This is my menu items name", vec!["foo", "bar", "baz"])
42/// ]);
43/// ```
44pub fn label<T: Into<String>>(text: T) -> TerminalMenuItem {
45 TerminalMenuItem {
46 name: text.into(),
47 kind: TMIKind::Label,
48 color: Color::Reset
49 }
50}
51
52/// Make a button terminal-menu item.
53/// Exits the menu with all the parent menus when pressed.
54/// # Example
55/// ```
56/// use terminal_menu::{menu, button, run, mut_menu};
57/// let my_menu = menu(vec![
58/// button("Alice"),
59/// button("Bob")
60/// ]);
61/// run(&my_menu);
62/// println!("Selected Button: {}", mut_menu(&my_menu).selected_item_name());
63/// ```
64pub fn button<T: Into<String>>(name: T) -> TerminalMenuItem {
65 TerminalMenuItem {
66 name: name.into(),
67 kind: TMIKind::Button,
68 color: Color::Reset
69 }
70}
71
72/// Make a back button terminal-menu item.
73/// Returns to the previous menu (or exits when there is none) when pressed.
74/// # Example
75/// ```
76/// use terminal_menu::{menu, back_button, submenu};
77/// let menu = menu(vec![
78/// submenu("Submenus Name", vec![
79/// back_button("Back")
80/// ]),
81/// back_button("Exit"),
82/// ]);
83/// ```
84pub fn back_button<T: Into<String>>(name: T) -> TerminalMenuItem {
85 TerminalMenuItem {
86 name: name.into(),
87 kind: TMIKind::BackButton,
88 color: Color::Reset
89 }
90}
91
92/// Make a terminal-menu item from which you can select a value from a selection.
93/// All values are dispalyed all the time.
94/// # Example
95/// ```
96/// use terminal_menu::{menu, scroll, run, mut_menu};
97/// let menu = menu(vec![
98/// scroll("My Scrolls Name", vec![
99/// "First Option",
100/// "Second Option",
101/// "Third Option"
102/// ])
103/// ]);
104/// run(&menu);
105/// println!("My Scrolls Value: {}", mut_menu(&menu).selection_value("My Scrolls Name"));
106/// ```
107pub fn scroll<T: Into<String>, T2: IntoIterator>(name: T, values: T2) -> TerminalMenuItem where T2::Item: Into<String> {
108 let values: Vec<String> = values.into_iter().map(|a| a.into()).collect();
109 if values.is_empty() {
110 panic!("values cannot be empty");
111 }
112 TerminalMenuItem {
113 name: name.into(),
114 kind: TMIKind::Scroll {
115 values,
116 selected: 0
117 },
118 color: Color::Reset
119 }
120}
121
122/// Make a terminal-menu item from which you can select a value from a selection.
123/// Only the selected value is visible.
124/// # Example
125/// ```
126/// use terminal_menu::{menu, list, run, mut_menu};
127/// let menu = menu(vec![
128/// list("My Lists Name", vec![
129/// "First Option",
130/// "Second Option",
131/// "Third Option"
132/// ])
133/// ]);
134/// run(&menu);
135/// println!("My Lists Value: {}", mut_menu(&menu).selection_value("My Lists Name"));
136/// ```
137pub fn list<T: Into<String>, T2: IntoIterator>(name: T, values: T2) -> TerminalMenuItem where T2::Item: Into<String> {
138 let values: Vec<String> = values.into_iter().map(|a| a.into()).collect();
139 if values.is_empty() {
140 panic!("values cannot be empty");
141 }
142 TerminalMenuItem {
143 name: name.into(),
144 kind: TMIKind::List {
145 values,
146 selected: 0
147 },
148 color: Color::Reset
149 }
150}
151
152/// Make a terminal-menu item which you can enter a string of characters to.
153/// Empty strings may be enabled with a flag.
154/// # Example
155/// ```
156/// use terminal_menu::{menu, string, run, mut_menu};
157/// let menu = menu(vec![
158/// string("My Strings Name", "Default Value", /* allow empty string */ false)
159/// ]);
160/// run(&menu);
161/// println!("My Strings Value: {}", mut_menu(&menu).selection_value("My Strings Name"));
162/// ```
163pub fn string<T: Into<String>, T2: Into<String>>(name: T, default: T2, allow_empty: bool) -> TerminalMenuItem {
164 TerminalMenuItem {
165 name: name.into(),
166 kind: TMIKind::String { value: default.into(), allow_empty },
167 color: Color::Reset,
168 }
169}
170
171/// Make a terminal-menu item which you can enter a string of hidden characters to. For example a password.
172/// Empty strings may be enabled with a flag.
173/// # Example
174/// ```
175/// use terminal_menu::{menu, string, run, mut_menu};
176/// let menu = menu(vec![
177/// password("My Password", "pass", /* allow empty string */ false)
178/// ]);
179/// run(&menu);
180/// println!("My Strings Value: {}", mut_menu(&menu).selection_value("My Password"));
181/// ```
182pub fn password<T: Into<String>, T2: Into<String>>(name: T, default: T2, allow_empty: bool) -> TerminalMenuItem {
183 TerminalMenuItem {
184 name: name.into(),
185 kind: TMIKind::Password { value: default.into(), allow_empty },
186 color: Color::Reset,
187 }
188}
189
190/// Make a terminal-menu item from which you can select a number between specified bounds.
191/// # Example
192/// ```
193/// use terminal_menu::{menu, numeric, run, mut_menu};
194/// let menu = menu(vec![
195/// numeric("My Numerics Name",
196/// 0.0, //default
197/// Some(0.5), //step (optional)
198/// Some(-5.0), //minimum (optional)
199/// Some(10.0) //maximum (optional)
200/// )
201/// ]);
202/// run(&menu);
203/// println!("My Numerics Value: {}", mut_menu(&menu).numeric_value("My Numerics Name"))
204/// ```
205pub fn numeric<T: Into<String>>(name: T, default: f64, step: Option<f64>, min: Option<f64>, max: Option<f64>) -> TerminalMenuItem {
206 if !utils::value_valid(default, step, min, max) {
207 panic!("invalid default value");
208 }
209 TerminalMenuItem {
210 name: name.into(),
211 kind: TMIKind::Numeric {
212 value: default,
213 step,
214 min,
215 max
216 },
217 color: Color::Reset
218 }
219}
220
221/// Make a terminal-menu submenu item.
222/// It is basically a menu inside a menu.
223/// # Example
224/// ```
225/// use terminal_menu::{menu, submenu, list, button, back_button, run, mut_menu};
226/// let menu = menu(vec![
227/// submenu("My Submenus Name", vec![
228/// list("List", vec!["First", "Second", "Third"]),
229/// back_button("Back"),
230/// button("Exit")
231/// ]),
232/// ]);
233/// run(&menu);
234/// println!("{}",
235/// mut_menu(&menu)
236/// .get_submenu("My Submenus Name")
237/// .selection_value("List"));
238/// ```
239pub fn submenu<T: Into<String> + Clone>(name: T, items: Vec<TerminalMenuItem>) -> TerminalMenuItem {
240 let menu = menu(items);
241 menu.write().unwrap().name = Some(name.clone().into());
242 TerminalMenuItem {
243 name: name.into(),
244 kind: TMIKind::Submenu(menu),
245 color: Color::Reset
246 }
247}
248
249impl TerminalMenuItem {
250
251 /// Get the name of the terminal-menu item.
252 pub fn name(&self) -> &str {
253 &self.name
254 }
255
256 /// Set a color to print the item in.
257 /// # Example
258 /// ```
259 /// use terminal_menu::{menu, label, scroll};
260 /// use crossterm::style::Color;
261 /// let menu = menu(vec![
262 /// label("Colorize me").colorize(Color::Magenta),
263 /// scroll("Me too!", vec!["foo", "bar"]).colorize(Color::Green)
264 /// ]);
265 /// ```
266 pub fn colorize(mut self, color: Color) -> Self {
267 self.color = color;
268 self
269 }
270
271}
272
273pub(crate) enum PrintState {
274 None,
275 Big
276}
277
278pub struct TerminalMenuStruct {
279 name: Option<String>,
280 pub items: Vec<TerminalMenuItem>,
281 selected: usize,
282 active: bool,
283 exited: bool,
284
285 longest_name: usize,
286 exit: Option<String>,
287 canceled: bool,
288 printed: PrintState,
289}
290impl TerminalMenuStruct {
291
292 /// Returns the name of the selected menu item.
293 /// # Example
294 /// ```
295 /// use terminal_menu::{menu, button, run, mut_menu};
296 /// let my_menu = menu(vec![
297 /// button("a"),
298 /// button("b"),
299 /// ]);
300 /// run(&my_menu);
301 /// println!("selected item name: {}", mut_menu(&my_menu).selected_item_name()); //"a" or "b"
302 /// ```
303 pub fn selected_item_name(&self) -> &str {
304 &self.items[self.selected].name
305 }
306
307 /// Returns the selected item as an index of the items vec.
308 /// # Example
309 /// ```
310 /// use terminal_menu::{menu, button, run, mut_menu};
311 /// let my_menu = menu(vec![
312 /// button("a"),
313 /// button("b"),
314 /// ]);
315 /// run(&my_menu);
316 /// println!("selected item index: {}", mut_menu(&my_menu).selected_item_index()); // 0 or 1
317 /// ```
318 pub fn selected_item_index(&self) -> usize {
319 self.selected
320 }
321
322 fn index_of(&self, name: &str) -> usize {
323 self.items.iter().position(|a| a.name == name).expect("No item with the given name")
324 }
325
326 /// Set the selected item with a name.
327 /// # Example
328 /// ```
329 /// use terminal_menu::{TerminalMenu, menu, button, mut_menu};
330 /// let my_menu: TerminalMenu = menu(vec![
331 /// button("item"),
332 /// button("other item")
333 /// ]);
334 /// mut_menu(&my_menu).set_selected_item_with_name("item");
335 /// ```
336 pub fn set_selected_item_with_name(&mut self, item: &str) {
337 self.selected = self.index_of(item);
338 }
339
340 /// Set the selected item with an index of the items vec.
341 /// # Example
342 /// ```
343 /// use terminal_menu::{TerminalMenu, menu, button, mut_menu};
344 /// let my_menu: TerminalMenu = menu(vec![
345 /// button("item"),
346 /// button("other item")
347 /// ]);
348 /// mut_menu(&my_menu).set_selected_item_with_index(1); //index 1 = other item
349 /// ```
350 pub fn set_selected_item_with_index(&mut self, item: usize) {
351 if item >= self.items.len() {
352 panic!("index out of bounds");
353 }
354 self.selected = item;
355 }
356
357 /// Returns the value of the specified scroll, list, or string item.
358 /// # Example
359 /// ```
360 /// use terminal_menu::{TerminalMenu, menu, scroll, run, mut_menu};
361 /// let my_menu: TerminalMenu = menu(vec![
362 /// scroll("item", vec!["val1", "val2"])
363 /// ]);
364 /// run(&my_menu);
365 /// println!("item value: {}", mut_menu(&my_menu).selection_value("item"));
366 /// ```
367 pub fn selection_value(&self, name: &str) -> &str {
368 match &self.items[self.index_of(name)].kind {
369 TMIKind::Scroll { values, selected } |
370 TMIKind::List { values, selected } => {
371 &values[*selected]
372 }
373 TMIKind::String { value, .. } | TMIKind::Password { value, .. } => value,
374 _ => panic!("item wrong kind")
375 }
376 }
377
378 /// Returns the value of the specified numeric item.
379 /// # Example
380 /// ```
381 /// use terminal_menu::{TerminalMenu, menu, scroll, run, numeric, mut_menu};
382 /// let my_menu: TerminalMenu = menu(vec![
383 /// numeric("item", 0.0, None, None, None)
384 /// ]);
385 /// run(&my_menu);
386 /// println!("item value: {}", mut_menu(&my_menu).numeric_value("item"));
387 /// ```
388 pub fn numeric_value(&self, name: &str) -> f64 {
389 match self.items[self.index_of(name)].kind {
390 TMIKind::Numeric { value, .. } => value,
391 _ => panic!("item wrong kind")
392 }
393 }
394
395 /// Returns the specified submenu.
396 /// # Example
397 /// ```
398 /// use terminal_menu::{TerminalMenu, menu, run, submenu, scroll, mut_menu};
399 /// let my_menu: TerminalMenu = menu(vec![
400 /// submenu("sub",vec![
401 /// scroll("item", vec!["winnie", "the", "pooh"])
402 /// ])
403 /// ]);
404 /// run(&my_menu);
405 /// println!("{}", mut_menu(&my_menu).get_submenu("sub").selection_value("item"));
406 /// ```
407 pub fn get_submenu(&mut self, name: &str) -> RwLockWriteGuard<TerminalMenuStruct> {
408 for item in &self.items {
409 if item.name == name {
410 if let TMIKind::Submenu(submenu) = &item.kind {
411 return submenu.write().unwrap();
412 }
413 }
414 }
415 panic!("Item not found or is wrong kind");
416 }
417
418 /// Returns the menu (or submenu) which was active on deactivation.
419 pub fn get_latest_menu_name(&mut self) -> Option<&str> {
420 match &self.exit {
421 None => None,
422 Some(a) => Some(a)
423 }
424 }
425
426 /// Returns true if menu was exited with 'q' or esc
427 /// # Example
428 /// ```
429 /// use terminal_menu::{menu, button, run, mut_menu};
430 /// let menu = menu(vec![
431 /// button("button")
432 /// ]);
433 /// run(&menu);
434 ///
435 /// // true if esc, false if button
436 /// println!("{}", mut_menu(&menu).canceled());
437 /// ```
438 pub fn canceled(&self) -> bool {
439 self.canceled
440 }
441
442}
443
444/// Create a terminal-menu. See the examples for more.
445/// # Example
446/// ```
447/// use terminal_menu::*;
448/// let my_menu = menu(vec![
449/// label("label"),
450/// button("button"),
451/// scroll("scroll", vec!["a", "b", "c"])
452/// ]);
453/// run(&my_menu);
454/// {
455/// let mm = mut_menu(&my_menu);
456/// println!("{}", mm.selection_value("scroll"));
457/// println!("{}", mm.selected_item_name());
458/// }
459/// ```
460pub fn menu(items: Vec<TerminalMenuItem>) -> TerminalMenu {
461 for i in 0..items.len() {
462 if let TMIKind::Label = items[i].kind {
463 } else {
464 return Arc::new(RwLock::new(TerminalMenuStruct {
465 name: None,
466 items,
467 selected: i,
468 active: false,
469 exited: true,
470
471 longest_name: 0,
472 exit: None,
473 canceled: false,
474 printed: PrintState::None,
475 }))
476 }
477 }
478 panic!("no selectable items");
479}
480
481/// Returns true if the menu has exited.
482pub fn has_exited(menu: &TerminalMenu) -> bool {
483 menu.read().unwrap().exited
484}
485
486/// Get a mutable instance of the menu.
487/// Works only if has_exited(&menu) is true.
488/// # Example
489/// ```
490/// use terminal_menu::{menu, numeric, string, run, has_exited, mut_menu};
491/// let mut my_menu = menu(vec![
492/// numeric("Charlie", 46.5, None, Some(32332.2), None)
493/// ]);
494/// run(&my_menu);
495///
496/// //stuff
497///
498/// {
499/// let mut mutable_menu = mut_menu(&my_menu);
500/// println!("Selected Item: {}", mutable_menu.selected_item_name());
501/// mutable_menu.items.push(string("new item", "def", false));
502/// }
503///
504/// run(&my_menu);
505///
506/// ```
507pub fn mut_menu(menu: &TerminalMenu) -> RwLockWriteGuard<TerminalMenuStruct> {
508 if !has_exited(menu) {
509 panic!("Cannot call mutable_instance if has_exited() is not true");
510 }
511 menu.write().unwrap()
512}
513
514/// Activate (open) the menu.
515/// Menu will deactivate when deactivated manually or button items are pressed.
516/// # Example
517/// ```
518/// use terminal_menu::{TerminalMenu, menu, activate, wait_for_exit};
519/// let my_menu = menu(vec![
520/// list("galadriel", vec!["frodo", "bilbo"])
521/// numeric("boo", 4.67, Some(3.0), None, None)
522/// ]);
523/// activate(&my_menu);
524///
525/// //do something here
526///
527/// wait_for_exit(&my_menu);
528///```
529pub fn activate(menu: &TerminalMenu) {
530 let menu = menu.clone();
531 thread::spawn(move || {
532 fancy_menu::run(menu.clone())
533 });
534}
535
536/// Wait for menu to exit.
537/// # Example
538/// ```
539/// use terminal_menu::{TerminalMenu, menu, activate, wait_for_exit};
540/// let my_menu = menu(vec![
541/// list("galadriel", vec!["frodo", "bilbo"])
542/// numeric("boo", 4.67, Some(3.0), None, None)
543/// ]);
544/// activate(&my_menu);
545///
546/// //do something here
547///
548/// wait_for_exit(&my_menu);
549///```
550pub fn wait_for_exit(menu: &TerminalMenu) {
551 loop {
552 thread::sleep(Duration::from_millis(10));
553 if has_exited(menu) {
554 break;
555 }
556 }
557}
558
559/// Activate the menu and wait for it to exit.
560/// # Example
561/// ```
562/// use terminal_menu::{TerminalMenu, menu, run};
563/// let my_menu = menu(vec![
564/// list("galadriel", vec!["frodo", "bilbo"])
565/// numeric("boo", 4.67, Some(3.0), None, None)
566/// ]);
567/// run(&my_menu);
568/// ```
569pub fn run(menu: &TerminalMenu) {
570 fancy_menu::run(menu.clone());
571}