yuno 0.1.1

A declarative UI layout and rendering framework powered by Skia.
use crate::drawing::{Drawable, Plan};
use crate::layouts::{HasPlanner, Layout};
use anyhow::bail;
use skia_safe::{Canvas, Rect};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Edges {
    Top,
    Left,
    Bottom,
    Right,
}

#[derive(Clone)]
pub struct EdgeLayoutElement {
    pub drawable: Rc<dyn Drawable>,

    pub l_edge: Edges,
    pub l_margin: f32,
    pub t_edge: Edges,
    pub t_margin: f32,
    pub r_edge: Edges,
    pub r_margin: f32,
    pub b_edge: Edges,
    pub b_margin: f32,
}

pub struct EdgeLayout {
    pub childs: RefCell<BTreeMap<String, EdgeLayoutElement>>,
    pub planner: RefCell<Option<Rc<dyn Layout>>>,
}

impl HasPlanner for EdgeLayout {
    fn planner(&self) -> &RefCell<Option<Rc<dyn Layout>>> {
        &self.planner
    }

    fn set_planner(&self, p: Option<Rc<dyn Layout>>) {
        *self.planner.borrow_mut() = p;
    }
}

impl Drawable for EdgeLayout {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        for (_key, element) in self.childs.borrow().iter() {
            if let Err(e) = element.drawable.draw(canvas) {
                println!("{:?}", e);
            }
        }
        Ok(())
    }

    fn get_horizontal_plan(&self) -> Plan {
        Plan::Fill
    }
    fn get_vertical_plan(&self) -> Plan {
        Plan::Fill
    }
}

impl Layout for EdgeLayout {
    fn measure_child(&self, c: &dyn Drawable, canvas: &Canvas) -> Rect {
        let parent_rect = self.get_planned_drawing_area(canvas);

        let childs = self.childs.borrow();
        let mut target_element = None;

        let c_ptr = c as *const dyn Drawable as *const ();

        for (_, element) in childs.iter() {
            let elem_ptr = &*element.drawable as *const dyn Drawable as *const ();
            if std::ptr::eq(c_ptr, elem_ptr) {
                target_element = Some(element.clone());
                break;
            }
        }

        if let Some(e) = target_element {
            let calc_edge = |edge: Edges, margin: f32| -> f32 {
                match edge {
                    Edges::Top => parent_rect.top + margin,
                    Edges::Bottom => parent_rect.bottom - margin,
                    Edges::Left => parent_rect.left + margin,
                    Edges::Right => parent_rect.right - margin,
                }
            };

            Rect {
                left: calc_edge(e.l_edge, e.l_margin),
                top: calc_edge(e.t_edge, e.t_margin),
                right: calc_edge(e.r_edge, e.r_margin),
                bottom: calc_edge(e.b_edge, e.b_margin),
            }
        } else {
            Rect::new(0.0, 0.0, 0.0, 0.0)
        }
    }
}

impl EdgeLayout {
    pub fn new() -> Rc<Self> {
        Rc::new(Self {
            childs: RefCell::new(BTreeMap::new()),
            planner: RefCell::new(None),
        })
    }

    pub fn add_child(self: &Rc<Self>, id: &str, e: EdgeLayoutElement) {
        e.drawable.set_planner(Some(self.clone()));
        self.childs.borrow_mut().insert(id.to_string(), e);
    }

    pub fn remove_child(&self, id: &str) {
        if let Some(c) = self.lookup_child(id) {
            c.drawable.set_planner(None);
            self.childs.borrow_mut().remove(id);
        }
    }

    pub fn lookup_child(&self, id: &str) -> Option<EdgeLayoutElement> {
        self.childs.borrow().get(id).cloned()
    }

    #[allow(clippy::too_many_arguments)]
    pub fn rewrite_child(
        &self,
        id: &str,
        l_edge: Edges,
        l_margin: f32,
        t_edge: Edges,
        t_margin: f32,
        r_edge: Edges,
        r_margin: f32,
        b_edge: Edges,
        b_margin: f32,
    ) -> anyhow::Result<()> {
        if let Some(v) = self.childs.borrow_mut().get_mut(id) {
            v.l_edge = l_edge;
            v.l_margin = l_margin;
            v.t_edge = t_edge;
            v.t_margin = t_margin;
            v.r_edge = r_edge;
            v.r_margin = r_margin;
            v.b_edge = b_edge;
            v.b_margin = b_margin;
            return Ok(());
        }

        bail!("Id not found")
    }
}