viewy 2.0.0

A web UI toolkit that combine the advantages of a design system and an ui library.
use hex::FromHex;
use uuid::Uuid;

use crate::components::{Alignment, Appendable, HStack, Text, TextStyle, View};
use crate::node::Node;
use crate::{DefaultModifiers, Renderable};

#[derive(Debug, Clone, Copy)]
pub struct Color(pub u8, pub u8, pub u8, pub u8);

impl Color {
    pub fn from_hex(hex: &str) -> Self {
        let color_string = hex.to_string().replace("#", "");
        let color_parsed = <[u8; 3]>::from_hex(color_string).expect("color format not valid");
        Color(color_parsed[0], color_parsed[1], color_parsed[2], 255)
    }

    pub fn from_alpha_hex(hex: &str) -> Self {
        let mut color_string = hex.to_string().replace("#", "");
        if color_string.len() == 6 {
            color_string = format!("{}ff", color_string);
        }
        let color_parsed = <[u8; 4]>::from_hex(color_string).expect("color format not valid");
        Color(
            color_parsed[0],
            color_parsed[1],
            color_parsed[2],
            color_parsed[3],
        )
    }

    pub fn to_string(&self) -> String {
        let color_array = [self.0, self.1, self.2];
        hex::encode(color_array)
    }
    pub fn to_css_value(&self) -> String {
        format!("#{}", self.to_string())
    }
}

#[derive(Debug, Clone)]
pub enum ColorPickerStyle {
    Palette(Vec<Color>),
    Free(Option<Color>),
}

impl ColorPickerStyle {
    pub fn get_style_name(&self) -> String {
        match self {
            ColorPickerStyle::Palette(_) => "palette",
            ColorPickerStyle::Free(_) => "free",
        }
        .to_string()
    }
}

#[derive(Debug, Clone)]
pub struct ColorPicker {
    node: Node,
    style: ColorPickerStyle,
    label: Option<String>,
    name: String,
}

impl ColorPicker {
    pub fn new(name: &str, style: ColorPickerStyle) -> Self {
        ColorPicker {
            node: Default::default(),
            style,
            label: None,
            name: name.to_string(),
        }
    }
    pub fn label(&mut self, label: &str) -> &mut Self {
        self.label = Some(label.to_string());
        self
    }
}

impl std::ops::Deref for ColorPicker {
    type Target = Node;

    fn deref(&self) -> &Self::Target {
        &self.node
    }
}

impl std::ops::DerefMut for ColorPicker {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.node
    }
}

impl DefaultModifiers for ColorPicker {}

impl Renderable for ColorPicker {
    fn render(mut self) -> Node {
        let base_class = format!("color-picker--{}", self.style.get_style_name());
        self.add_class("color-picker").add_class(&base_class);
        if let Some(label) = &self.label {
            self.node
                .children
                .push({ Text::new(label, TextStyle::Label) }.render());
        }
        let picker_id = Uuid::new_v4().to_string();
        match &self.style {
            ColorPickerStyle::Palette(color_palette) => self.node.children.push({
                let mut option_list = HStack::new(Alignment::Center);
                option_list.add_class(&format!("{}--option-list", &base_class));
                for color in color_palette {
                    let radio_id = format!("color-picker-{}-{}", picker_id, color.to_string());
                    option_list.append_child({
                        let mut input = View::new();
                        input
                            .tag("input")
                            .set_attr("type", "radio")
                            .set_attr("name", self.name.as_str())
                            .set_attr("value", &format!("#{}", color.to_string()))
                            .set_attr("id", radio_id.as_str())
                            .add_class(&format!("{}--option-list--radio", &base_class));
                        input
                    });
                    option_list.append_child({
                        let mut label = View::new();
                        label
                            .tag("label")
                            .set_attr("for", radio_id.as_str())
                            .color(&color.to_css_value())
                            .add_class(&format!("{}--option-list--swatch", &base_class));
                        label
                    });
                }
                option_list.render()
            }),
            ColorPickerStyle::Free(color) => {
                let color_hex_code = format!(
                    "#{}",
                    color.unwrap_or(Color::from_hex("#ffffff")).to_string()
                );
                self.node.children.push({
                    let mut option_list = View::new();
                    option_list
                        .tag("input")
                        .set_attr("name", self.name.as_str())
                        .set_attr("value", { &color_hex_code })
                        .set_attr("type", "color")
                        .add_class(&format!("{}--input", &base_class));

                    option_list.render()
                })
            }
        }
        self.node
    }
}