cli_printer 0.2.0

A library for create a cli apps
Documentation
use core::time;

use crossterm::{
    event::{self, poll, KeyCode},
    execute,
    style::{Print, Stylize},
};

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

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

pub struct Input<'a, T> {
    text_init: IconAndLabel<'a>,
    text_final: IconAndLabel<'a>,
    cb_after: Box<AfterCb<T>>,
    cb_before: Box<BeforeCb<T>>,
    local_state: InputData,
}

#[derive(Clone)]
pub struct InputData {
    pub input: String,
    pub is_hidden: bool,
    pub complete_input: bool,
}

impl<'a, T: Clone> Widget for Input<'a, T> {
    fn render(&mut self, stdout: &mut std::io::Stdout) -> std::io::Result<()> {
        if !self.local_state.complete_input {
            execute!(
                stdout,
                Print(self.text_init.0.cyan()),
                Print(self.text_init.1),
            )?;
            if !self.local_state.is_hidden {
                execute!(stdout, Print(self.local_state.input.as_str().dark_grey()))?;
            }
            if poll(time::Duration::from_millis(80))? {
                match event::read()? {
                    event::Event::Key(k) => {
                        let code = k.code;
                        if code == KeyCode::Enter {
                            if self.local_state.input.len() != 0 {
                                self.local_state.complete_input = true;
                            }
                        } else if code == KeyCode::Backspace {
                            self.local_state.input.pop();
                        }
                        if let KeyCode::Char(c) = code {
                            self.local_state.input.push(c);
                        }
                    }
                    _ => {}
                }
            }
            return Ok(());
        }
        execute!(
            stdout,
            Print(self.text_final.0.green()),
            Print(self.text_final.1),
            Print(self.local_state.input.as_str().dark_grey())
        )?;
        Ok(())
    }
}

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

impl<'a, T: Clone> Default for Input<'a, T> {
    fn default() -> Self {
        Self {
            text_init: IconAndLabel(ICON_QUESTION, "Write: "),
            text_final: IconAndLabel(ICON_CHECK, "Write: "),
            cb_after: Box::new(|input, _gloabl_state| {
                if input.complete_input && input.input.len() > 0 {
                    return Action::Next;
                }
                Action::KeepSection
            }),

            local_state: InputData {
                input: String::new(),
                is_hidden: false,
                complete_input: false,
            },
            cb_before: Box::new(|_, _| RenderWidget::Yes),
        }
    }
}

impl<'a, T: Clone> Input<'a, T> {
    pub fn new(text_init: IconAndLabel<'a>, text_final: IconAndLabel<'a>) -> Self {
        Self {
            text_init,
            text_final,
            cb_after: Box::new(|input, _gloabl_state| {
                if input.complete_input && input.input.len() > 0 {
                    return Action::Next;
                }
                Action::KeepSection
            }),
            local_state: InputData {
                input: String::new(),
                is_hidden: false,
                complete_input: false,
            },
            cb_before: Box::new(|_, _| RenderWidget::Yes),
        }
    }
    pub fn hidden(&mut self, hidden: bool) {
        self.local_state.is_hidden = hidden
    }
    pub fn after(&mut self, cb: impl FnMut(&mut InputData, T) -> Action + 'static) {
        self.cb_after = Box::new(cb);
    }
    pub fn before(&mut self, cb: impl FnMut(&mut InputData, T) -> RenderWidget + 'static) {
        self.cb_before = Box::new(cb);
    }
}