ask 0.0.3

A toolset for nicely displayed Questions and Answers through the terminal.
Documentation
pub struct Ask<T>{
    questions: Vec<T>
}

impl <T>Ask<T> 
    where T : question::TraitQuestion {
    pub fn new()->Ask<T> {
        Ask{
            questions: vec!()
        }
    }

    pub fn question(&mut self, question_obj:T){
        self.questions.push(question_obj);
    }

    pub fn all(&mut self){
        for question in &mut self.questions {
            &question.send();
        }
    }
}

pub mod question{
    extern crate termion;
    use std::io::{Write, stdout, Stdout, stdin};
    use question::termion::{input::TermRead, event::Key};
    use question::termion::raw::IntoRawMode;

    pub trait TraitQuestion{
        fn send(&mut self)->Result<Vec<QuestionOption>, String>;
    }

    pub struct OpenEnded{
        message      : String,
        pub response : Option<String>,
    }

    type QuestionOption = String;

    pub struct MultipleChoice{
        message              : String,
        options              : Vec<QuestionOption>, //TODO: fold this into an options struct
        options_len          : u16,
        options_selected     : Vec<bool>,   //TODO: fold this into an options struct
        max_selections       : u16,
        min_selections       : u16,
    }


    impl TraitQuestion for MultipleChoice{
        fn send(&mut self)->Result<Vec<QuestionOption>, String>{
            let mut selector_index : u16 = 0;

            let mut stdout  = stdout().into_raw_mode().unwrap();
            let stdin       = stdin();
            let mut size    = termion::terminal_size().unwrap();

            write!(stdout, "{}{}{}",  self.message, termion::scroll::Up(1), termion::cursor::Goto(0,size.1));
            stdout.flush().unwrap();

            let result = if self.options.len() < 2{
                Err("2 or more options must be populated before sending multiple choice.".to_owned())
            }else{
                //make room for all the options
                write!(stdout,"{}", termion::scroll::Up(self.options_len));
                self.render_options(&mut stdout, selector_index, size);
                for c in stdin.keys(){
                    size = termion::terminal_size().unwrap();
                    match c.unwrap(){
                        // Key::Char('\n') => {
                        //     write!(stdout, "{}{}",
                        //     termion::scroll::Up(1),
                        //     termion::cursor::Goto(0,term_size.1),
                        //     ).unwrap();
                        // },
                        Key::Down => {
                            if selector_index != self.options_len{
                                selector_index += 1;
                            }
                        },
                        Key::Up =>{
                            if selector_index != 0{
                                selector_index -= 1;
                            }
                        },
                        Key::Char('\n') =>{
                            if selector_index < self.options_len{ // our selector is on an options
                                if self.any_amount() || self.max_selections > self.get_selection_count(){
                                    self.options_selected[selector_index as usize] = !self.options_selected[selector_index as usize];
                                }else { // can toggle off
                                    self.options_selected[selector_index as usize] = false;
                                }
                            }else{
                                // our selector is on the continue.
                                // check if can continue.
                                if self.can_continue() {
                                    break;
                                }else{
                                    print!("Cant Continue must be between min max selections.");
                                }
                            }
                        },
                        Key::Ctrl('c') =>{
                            return Err("Terminated process.".to_string());
                        },
                        Key::Char(c) => {
                            print!("{}",c);
                        },
                        _ => {}
                    }
                    self.render_options(&mut stdout, selector_index, size);
                }
                Ok(self.get_all_selected_options())
            };
            stdout.flush().unwrap();
            result
        }
        //return the options selected
    }


    impl MultipleChoice{
        pub fn new(message:String) -> MultipleChoice{
            MultipleChoice{
                message          : message,
                options          : vec!(),
                options_len      : 0,
                options_selected : vec!(),
                max_selections   : 0,
                min_selections   : 0,
            }
        }

        fn get_all_selected_options(&self)->Vec<QuestionOption>{
            let mut i = 0;
            let mut result = vec!();
            for &is_selected in &self.options_selected{
                if is_selected {
                    result.push(self.options[i].clone());
                }
                i += 1;
            }

            return result;
        }

        fn get_selection_count(&self)->u16 {
            self.options_selected.iter().fold(0,|acc, x| {if x == &true{acc + 1}else{acc} }) as u16
        }

        fn get_all_selected_indexies(&self)->Vec<usize>{
            let mut i = 0;
            let mut result = vec!();
            for &is_selected in &self.options_selected{
                if is_selected {
                    result.push(i);
                }
                i += 1;
            }

            return result;
        }

        fn render_options(&self, stdout: &mut termion::raw::RawTerminal<Stdout>, selector_index: u16, size : (u16, u16)){
            let mut i = 0;

            for option in &self.options{
                let selector_str = if i == selector_index {
                    let selector = vec!(0xE2, 0x98, 0x9B);
                    String::from_utf8_lossy(&selector).into_owned()
                }else{
                    " ".to_owned()
                };

                let selected_icon = if self.options_selected[i as usize] {
                    let large_filled_circle = vec!(0xE2, 0xAC, 0xA4);
                    String::from_utf8_lossy(&large_filled_circle).into_owned()
                }else{
                    let large_circle = vec!(0xE2, 0x97, 0xAF);
                    String::from_utf8_lossy(&large_circle).into_owned()
                };

                write!(stdout, "{}{}{}{}   {}  {}", termion::cursor::Goto(0,size.1 - self.options_len + i), termion::clear::CurrentLine, termion::clear::CurrentLine, selector_str, selected_icon, option );
                i += 1;
            }

            let selector_str = if i == selector_index {
                let selector = vec!(0xE2, 0x98, 0x9B);
                String::from_utf8_lossy(&selector).into_owned()
            }else{
                " ".to_owned()
            };
            let continue_str = if self.can_continue(){"> Continue >"}else{"[ Continue ]"};
            write!(stdout, "{}{}{}   {}", termion::cursor::Goto(0,size.1 - self.options_len + i), termion::clear::CurrentLine, selector_str, continue_str );

            stdout.flush().unwrap();
        }


        fn can_continue(& self)->bool{
            //is between min and max . if min and max are both 0 then just continue
            return if self.any_amount(){
                true
            }else if  self.get_selection_count() >= self.min_selections &&
                      self.get_selection_count() <= self.max_selections{
                true
            }else{
                false
            }
        }

        fn any_amount(& self)->bool{
            //is between min and max . if min and max are both 0 then just continue
            return if self.min_selections == 0 && self.max_selections == 0{
                true
            }else{
                false
            }
        }

        //=== Accessors ===
        pub fn add_option(&mut self, option:String)->&mut Self{
            self.options.push(option);
            self.options_len += 1;
            self.options_selected.push(false);
            self
        }

        pub fn max_selections(&mut self, number:u16)->&mut Self{
            self.max_selections = number;
            self
        }

        pub fn min_selections(&mut self, number:u16)->&mut Self{
            self.min_selections = number;
            self
        }

    }

    impl TraitQuestion for OpenEnded{

        fn send(&mut self)->Result<Vec<QuestionOption>, String>{
            let mut stdout = stdout().into_raw_mode().unwrap();
            let stdin      = stdin();
            let mut size   = termion::terminal_size().unwrap();
            write!(stdout, "{}{}{}",  self.message, termion::scroll::Up(1), termion::cursor::Goto(0,size.1));
            stdout.flush().unwrap();
            let mut data : Vec<char> = vec!();
            for c in stdin.keys(){

                size = termion::terminal_size().unwrap();
                match c.unwrap(){
                    // Key::Char('\n') => {
                    //     write!(stdout, "{}{}",
                    //     termion::scroll::Up(1),
                    //     termion::cursor::Goto(0,term_size.1),
                    //     ).unwrap();
                    // },   return Err("Terminated process.");
                    Key::Char(c)   => {
                        data.push(c);
                        write!(stdout,"{}",c);
                    }
                    Key::Ctrl('c') => break, 
                    // Key::Char(c) => println!("{}",c),
                    _ => {}
                }

                stdout.flush().unwrap();
            }

            write!(stdout, "{}{}",termion::cursor::Goto(0, size.1+1), termion::scroll::Up(1)); //new line
            let content = data.iter().cloned().collect::<String>();
            self.response = Some(content.clone());    

            Ok(vec!(content))
            // write!(stdout, "{}",termion::cursor::Goto(0, size.1+1)); //new line
        }
    }
    impl OpenEnded{
        pub fn new(message:String) -> OpenEnded{
            OpenEnded{
                message  :message,
                response :None,
            }
        }
    }
}