cli_printer 0.2.0

A library for create a cli apps
Documentation
use crossterm::{
    event::{self, Event, KeyCode},
    execute,
    style::{Print, Stylize},
};

use crate::{
    core::{
        interfaces::{Widget, WidgetChild},
        utils::{Action, IconAndLabel, RenderWidget},
    },
    styles::{ICON_CHECK, ICON_QUESTION},
};

use super::list;

type AfterCb<T> = dyn FnMut(&mut ListSelectedData, T) -> Action;

type BeforeCb<T> = dyn FnMut(&mut ListSelectedData, T) -> RenderWidget;

pub struct ListSelected<'a, T> {
    pub list: list::List<'a>,
    cb_before: Box<BeforeCb<T>>,
    cb_after: Box<AfterCb<T>>,
    text_init: IconAndLabel<'a>,
    text_final: IconAndLabel<'a>,
    local_state: ListSelectedData,
}

pub struct ListSelectedData {
    pub is_selected: bool,
    pub offset: usize,
    pub current_option: Option<String>,
    pub length: usize,
}

impl<'a, T: Clone> Widget for ListSelected<'a, T> {
    fn render(&mut self, stdout: &mut std::io::Stdout) -> std::io::Result<()> {
        if !self.local_state.is_selected {
            execute!(
                stdout,
                Print(self.text_init.0.cyan()),
                Print(self.text_init.1),
                Print("\n")
            )?;
            self.list.render(stdout)?;
            match event::read()? {
                Event::Key(k) => {
                    let code = k.code;
                    if code == KeyCode::Down {
                        self.list.next()
                    } else if code == KeyCode::Up {
                        self.list.prev()
                    } else if code == KeyCode::Enter {
                        self.local_state.is_selected = true
                    }
                    self.local_state.offset = self.list.state.offset.clone();
                    self.local_state.current_option = match self.list.get_current_index().1 {
                        Some(l) => Some(l.0.to_string()),
                        None => None,
                    };
                }
                _ => {}
            }
            return Ok(());
        }

        let option_selected = self.list.get_current_index();
        let text_selected = match option_selected.1 {
            Some(t) => t.0,
            None => return Ok(()),
        };

        execute!(
            stdout,
            Print(self.text_final.0.green()),
            Print(self.text_final.1),
            Print(text_selected.dark_grey()),
        )?;

        Ok(())
    }
}

impl<'a, T: Clone> WidgetChild<T> for ListSelected<'a, T> {
    fn after_render(&mut self, global_state: T) -> Action {
        (self.cb_after)(&mut self.local_state, global_state)
    }

    fn before_render(&mut self, global_state: T) -> RenderWidget {
        (self.cb_before)(&mut self.local_state, global_state)
    }
}

impl<'a, T: Clone> ListSelected<'a, T> {
    pub fn new(options: Vec<&'a str>) -> Self {
        let length = options.len();
        Self {
            list: list::List::new(options),
            cb_after: Box::new(|_, _| Action::Next),
            cb_before: Box::new(|_, _| RenderWidget::Yes),
            text_init: IconAndLabel(ICON_QUESTION, "Choose an option: "),
            text_final: IconAndLabel(ICON_CHECK, "Option selected: "),
            local_state: ListSelectedData {
                is_selected: false,
                offset: 0,
                current_option: None,
                length,
            },
        }
    }
    pub fn after(&mut self, cb: impl FnMut(&mut ListSelectedData, T) -> Action + 'static) {
        self.cb_after = Box::new(cb);
    }
    pub fn before(&mut self, cb: impl FnMut(&mut ListSelectedData, T) -> RenderWidget + 'static) {
        self.cb_before = Box::new(cb);
    }
    pub fn add_text_init(&mut self, icon: &'a str, label: &'a str) {
        self.text_init = IconAndLabel(icon, label);
    }
    pub fn add_text_final(&mut self, icon: &'a str, label: &'a str) {
        self.text_final = IconAndLabel(icon, label);
    }
}