menu_rs 0.1.0

A simple command line menu
Documentation
use console::{Key, Style, Term};

#[derive(Debug)]
pub struct MenuOption {
    label: String,
    func: fn() -> (),
    hint: Option<String>,
}

impl MenuOption {
    pub fn new(label: &str, func: fn() -> ()) -> MenuOption {
        return MenuOption {
            label: label.to_owned(),
            func: func,
            hint: None,
        };
    }

    pub fn hint(mut self, text: &str) -> MenuOption {
        self.hint = Some(text.to_owned());
        return self;
    }
}

#[derive(Debug)]
pub struct Menu {
    options: Vec<MenuOption>,
    selected_option: usize,
    selected_style: Style,
    normal_style: Style,
    hint_style: Style,
    action_func: fn() -> (),
}

impl Menu {
    pub fn new(options: Vec<MenuOption>) -> Menu {
        return Menu {
            options: options,
            selected_option: 0,
            selected_style: Style::new().on_blue(),
            normal_style: Style::new(),
            hint_style: Style::new().color256(187),
            action_func: dummy_func,
        };
    }

    pub fn show(mut self) {
        let stdout = Term::buffered_stdout();
        stdout.hide_cursor().unwrap();

        // clears screen and shows the menu
        stdout.clear_screen().unwrap();
        self.draw_menu(&stdout);

        // runs menu navigation
        self.menu_navigation(&stdout);

        // clears screen and runs action function before exiting
        stdout.clear_screen().unwrap();
        stdout.flush().unwrap();
        (self.action_func)();
    }

    fn menu_navigation(&mut self, stdout: &Term) {
        let options_limit_num = self.options.len() - 1;
        loop {
            // gets pressed key
            let key = match stdout.read_key() {
                Ok(val) => val,
                Err(_e) => {
                    println!("Error reading key");
                    return;
                }
            };

            // handles key press
            match key {
                Key::ArrowUp => {
                    self.selected_option = match self.selected_option == 0 {
                        true => options_limit_num,
                        false => self.selected_option - 1,
                    }
                }
                Key::ArrowDown => {
                    self.selected_option = match self.selected_option == options_limit_num {
                        true => 0,
                        false => self.selected_option + 1,
                    }
                }
                Key::Escape => return,
                Key::Enter => {
                    self.action_func = self.options[self.selected_option].func;
                    return;
                }
                // Key::Char(c) => println!("char {}", c),
                _ => {}
            }

            self.draw_menu(stdout);
        }
    }

    fn draw_menu(&self, stdout: &Term) {
        // clears screen
        stdout.clear_screen().unwrap();

        // draw menu to stdout
        for (i, option) in self.options.iter().enumerate() {
            let label_style = match i == self.selected_option {
                true => self.selected_style.clone(),
                false => self.normal_style.clone(),
            };

            // builds menu pieces
            let label = label_style.apply_to(option.label.as_str());
            let hint_str = match &self.options[i].hint {
                Some(hint) => hint,
                None => "",
            };
            let hint = self.hint_style.apply_to(hint_str);

            // builds and writes build line
            let line = format!("- {: <25}\t{}", label, hint);
            stdout.write_line(line.as_str()).unwrap();
        }

        // draws to terminal
        stdout.flush().unwrap();
    }
}

fn dummy_func() {}