bernardo-tui 0.2.7

A keyboard-only, distraction-free TUI widget library
Documentation
use std::process::Output;

use log::warn;

use super::text_widget::TextWidget;
use crate::config::theme::Theme;
use crate::io::input_event::InputEvent::KeyInput;
use crate::io::keys::Keycode;
use crate::io::sub_output::SubOutput;
use crate::primitives::color::Color;
use crate::primitives::rect::Rect;
use crate::primitives::xy::XY;
use crate::widget::any_msg::AnyMsg;
use crate::widget::widget::{get_new_widget_id, Widget, WID};

pub struct CheckBoxWidget {
    wid: WID,
    checked: bool,
    label: TextWidget,
    text_widget_theme: Theme,
}

impl CheckBoxWidget {
    pub const TYPENAME: &'static str = "check_box";
    const CHECK_SYMBOL_CHECKED: &'static str = "[X]";
    const CHECK_SYMBOL_UNCHECKED: &'static str = "[ ]";
    const CHECK_SYMBOL_SIZE: u16 = 3;

    pub fn new(label: TextWidget) -> Self {
        let clicked_color = Color::new(255, 255, 153);
        let mut theme = Theme::default();
        theme.ui.focused.set_foreground(clicked_color);
        theme.ui.non_focused.set_foreground(clicked_color);

        Self {
            wid: get_new_widget_id(),
            checked: false,
            label,
            text_widget_theme: theme,
        }
    }

    pub fn with_checked(self, checked: bool) -> Self {
        Self { checked, ..self }
    }

    pub fn is_checked(&self) -> bool {
        self.checked
    }

    pub fn toggle(&mut self) {
        self.checked = !self.checked;
    }
}

impl Widget for CheckBoxWidget {
    fn id(&self) -> WID {
        self.wid
    }

    fn static_typename() -> &'static str
    where
        Self: Sized,
    {
        Self::TYPENAME
    }

    fn typename(&self) -> &'static str {
        Self::TYPENAME
    }

    fn full_size(&self) -> crate::primitives::xy::XY {
        let mut size = self.label.full_size();
        size.x += Self::CHECK_SYMBOL_SIZE;
        size
    }

    fn layout(&mut self, screenspace: crate::experiments::screenspace::Screenspace) {}

    fn on_input(&self, input_event: crate::io::input_event::InputEvent) -> Option<Box<dyn crate::widget::any_msg::AnyMsg>> {
        return match input_event {
            KeyInput(key_event) => match key_event.keycode {
                Keycode::Enter => Some(Box::new(CheckBoxMsg::Hit)),
                _ => None,
            },
            _ => None,
        };
    }

    fn update(&mut self, msg: Box<dyn crate::widget::any_msg::AnyMsg>) -> Option<Box<dyn crate::widget::any_msg::AnyMsg>> {
        let our_msg = msg.as_msg::<CheckBoxMsg>();
        if our_msg.is_none() {
            warn!("expecetd CheckBoxMsg, got {:?}", msg);
            return None;
        }
        self.toggle();
        None
    }

    fn render(&self, theme: &crate::config::theme::Theme, focused: bool, output: &mut dyn crate::io::output::Output) {
        let text = theme.default_text(focused);
        let (checked_symbol, label_theme) = if self.checked {
            (Self::CHECK_SYMBOL_CHECKED, &self.text_widget_theme)
        } else {
            (Self::CHECK_SYMBOL_UNCHECKED, theme)
        };

        output.print_at(XY::ZERO, text, &checked_symbol);

        let label_rect = Rect::new(
            XY::new(Self::CHECK_SYMBOL_SIZE + 1, 0),
            XY::new(output.size().x - (Self::CHECK_SYMBOL_SIZE + 1), output.size().y),
        );
        let sub_output = &mut SubOutput::new(output, label_rect);
        self.label.render(label_theme, focused, sub_output);
    }
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum CheckBoxMsg {
    Hit,
}

impl AnyMsg for CheckBoxMsg {}