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, Point, Rect};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::rc::Rc;

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BoxArrangement {
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
    Center,
}

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

    pub arrangement: BoxArrangement,
    pub offset: Point,
}

pub struct BoxLayout {
    w: RefCell<Option<f32>>,
    h: RefCell<Option<f32>>,

    pub childs: RefCell<BTreeMap<String, BoxLayoutElement>>,
    pub planner: RefCell<Option<Rc<dyn Layout>>>,
}

impl HasPlanner for BoxLayout {
    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 BoxLayout {
    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 {
        match *self.w.borrow() {
            Some(w) => Plan::Fit(w),
            None => Plan::Fill,
        }
    }

    fn get_vertical_plan(&self) -> Plan {
        match *self.h.borrow() {
            Some(h) => Plan::Fit(h),
            None => Plan::Fill,
        }
    }
}

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

        let (arrangement, offset) = {
            let childs = self.childs.borrow();
            let target_ptr = c as *const _ as *const ();

            let found = childs
                .values()
                .find(|el| (el.drawable.as_ref() as *const _ as *const ()) == target_ptr);

            match found {
                Some(el) => (el.arrangement, el.offset),
                None => return Rect::new(0.0, 0.0, 0.0, 0.0),
            }
        };

        let h_plan = c.get_horizontal_plan();
        let v_plan = c.get_vertical_plan();

        let child_w = match h_plan {
            Plan::Fit(w) => w,
            _ => parent_area.width(),
        };
        let child_h = match v_plan {
            Plan::Fit(h) => h,
            _ => parent_area.height(),
        };

        let (mut x, mut y) = match arrangement {
            BoxArrangement::TopLeft => (parent_area.left, parent_area.top),
            BoxArrangement::TopRight => (parent_area.right - child_w, parent_area.top),
            BoxArrangement::BottomLeft => (parent_area.left, parent_area.bottom - child_h),
            BoxArrangement::BottomRight => {
                (parent_area.right - child_w, parent_area.bottom - child_h)
            }
            BoxArrangement::Center => (
                parent_area.left + (parent_area.width() - child_w) / 2.0,
                parent_area.top + (parent_area.height() - child_h) / 2.0,
            ),
        };

        x += offset.x;
        y += offset.y;

        Rect::from_xywh(x, y, child_w, child_h)
    }
}

impl Default for BoxLayout {
    fn default() -> Self {
        Self::new()
    }
}

impl BoxLayout {
    pub fn new() -> Self {
        Self {
            w: RefCell::new(None),
            h: RefCell::new(None),
            childs: RefCell::new(BTreeMap::new()),
            planner: RefCell::new(None),
        }
    }

    pub fn set_w(&self, w: Option<f32>) {
        *self.w.borrow_mut() = w;
    }

    pub fn set_h(&self, h: Option<f32>) {
        *self.h.borrow_mut() = h;
    }

    pub fn add_child(self: &Rc<Self>, id: &str, e: BoxLayoutElement) {
        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<BoxLayoutElement> {
        self.childs.borrow().get(id).cloned()
    }

    #[allow(clippy::too_many_arguments)]
    pub fn rewrite_child(&self, id: &str, r: BoxArrangement, p: Point) -> anyhow::Result<()> {
        if let Some(v) = self.childs.borrow_mut().get_mut(id) {
            v.arrangement = r;
            v.offset = p;
            return Ok(());
        }

        bail!("Id not found")
    }
}