deft 0.15.0

Cross platform ui framework
use crate as deft;
use crate::base::EventContext;
use crate::element::container::Container;
use crate::element::image::Image;
use crate::element::label::Label;
use crate::element::{Element, ElementBackend, ElementWeak};
use crate::event::{ClickEvent, Event};
use crate::style::length::LengthOrPercent;
use crate::style::{FixedStyleProp, ResolvedStyleProp, StylePropVal};
use crate::{ok_or_return, some_or_return};
use deft_macros::{element_backend, event, js_methods};
use std::collections::HashMap;
use yoga::{Align, Display, FlexDirection};

#[element_backend]
pub struct Radio {
    element: ElementWeak,
    base: Container,
    img_element: Element,
    wrapper_element: Element,
    box_element: Element,
    label_element: Element,
    checked: bool,
}

#[event]
pub struct ChangeEvent {}

fn find_group(mut p: Element) -> Option<Element> {
    loop {
        p = p.get_parent()?;
        if p.is_backend::<RadioGroup>() {
            return Some(p);
        }
    }
}

#[js_methods]
impl Radio {
    #[js_func]
    pub fn set_label(&mut self, label: String) {
        self.label_element
            .get_backend_mut_as::<Label>()
            .set_text(label);
    }

    #[js_func]
    pub fn get_label(&mut self) -> String {
        self.label_element.get_backend_mut_as::<Label>().get_text()
    }

    #[js_func]
    pub fn is_checked(&self) -> bool {
        self.checked
    }

    #[js_func]
    pub fn set_checked(&mut self, checked: bool) {
        let mut el = ok_or_return!(self.element.upgrade());
        if checked {
            el.set_attribute("checked".to_string(), "".to_string());
        } else {
            el.remove_attribute("checked".to_string());
        }
    }

    fn update_checked(&mut self, checked: bool) {
        if self.checked != checked {
            if checked {
                self.uncheck_siblings();
            }
            self.checked = checked;
            self.update_children();
            self.element.emit(ChangeEvent {});
        }
    }

    fn uncheck_siblings(&mut self) {
        let element = ok_or_return!(self.element.upgrade());
        let mut group = some_or_return!(find_group(element)).clone();
        Self::uncheck_children_recurse(&mut group);
    }

    fn uncheck_children_recurse(element: &mut Element) {
        if element.is_backend::<Radio>() {
            let radio = element.get_backend_mut_as::<Radio>();
            radio.set_checked(false);
        } else {
            for mut c in element.get_children() {
                Self::uncheck_children_recurse(&mut c);
            }
        }
    }

    fn update_children(&mut self) {
        let display = if self.checked {
            Display::Flex
        } else {
            Display::None
        };
        self.img_element
            .set_style_props(vec![FixedStyleProp::Display(StylePropVal::Custom(display))]);
    }
}

impl ElementBackend for Radio {
    fn create(element: &mut Element) -> Self
    where
        Self: Sized,
    {
        element.is_form_element = true;
        let base = Container::create(element);
        let mut wrapper_element = Element::create(Container::create);
        let mut box_element = Element::create(Container::create);
        let label_element = Element::create(Label::create);
        let mut img_element = Element::create(Image::create);
        img_element
            .get_backend_mut_as::<Image>()
            .set_src_svg_raw(include_bytes!("./selected.svg"));
        img_element.set_style_props(vec![
            FixedStyleProp::Width(StylePropVal::Custom(LengthOrPercent::Percent(100.0))),
            FixedStyleProp::Height(StylePropVal::Custom(LengthOrPercent::Percent(100.0))),
        ]);
        box_element.add_child(img_element.clone(), 0).unwrap();

        wrapper_element.add_child(box_element.clone(), 0).unwrap();
        wrapper_element.add_child(label_element.clone(), 1).unwrap();

        element.add_child(wrapper_element.clone(), 0).unwrap();
        wrapper_element.set_style_props(vec![
            FixedStyleProp::AlignItems(StylePropVal::Custom(Align::Center)),
            FixedStyleProp::FlexDirection(StylePropVal::Custom(FlexDirection::Row)),
        ]);
        let mut inst = RadioData {
            element: element.as_weak(),
            base,
            img_element,
            wrapper_element,
            box_element,
            label_element,
            checked: false,
        }
        .to_ref();
        inst.update_children();
        inst
    }

    fn get_base_mut(&mut self) -> Option<&mut dyn ElementBackend> {
        Some(&mut self.base)
    }

    fn on_event(&mut self, event: &mut Event, ctx: &mut EventContext<ElementWeak>) {
        if ClickEvent::is(event) {
            self.update_checked(true);
        } else {
            self.base.on_event(event, ctx);
        }
    }

    fn accept_pseudo_element_styles(&mut self, styles: HashMap<String, Vec<ResolvedStyleProp>>) {
        if let Some(styles) = styles.get("box") {
            let styles = styles.iter().map(|s| s.to_unresolved()).collect::<Vec<_>>();
            self.box_element.set_style_props(styles);
        }
    }
    fn on_attribute_changed(&mut self, key: &str, value: Option<&str>) {
        match key {
            "checked" => self.update_checked(value.is_some()),
            _ => self.base.on_attribute_changed(key, value),
        }
    }
}

#[element_backend]
pub struct RadioGroup {
    base: Container,
}

impl ElementBackend for RadioGroup {
    fn create(element: &mut Element) -> Self
    where
        Self: Sized,
    {
        element.register_js_event::<ChangeEvent>("change");
        let base = Container::create(element);
        RadioGroupData { base }.to_ref()
    }

    fn get_base_mut(&mut self) -> Option<&mut dyn ElementBackend> {
        Some(&mut self.base)
    }
}