yuno 0.2.1

Multimedia UI layout and rendering framework powered by Skia.
use crate::drawing::{Drawable, Plan};
use crate::interacting::{Event, Interactive};
use crate::layouts::{HasPlanner, Layout};
use anyhow::bail;
use skia_safe::{Canvas, Rect};
use std::any::Any;
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>>>,

    enabled: RefCell<bool>,
}

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 !element.drawable.is_enabled() {
                continue;
            }

            if let Err(e) = element.drawable.draw(canvas) {
                println!("EdgeLayout draw error at child {}: {:?}", key, e);
            }
        }
        Ok(())
    }

    fn get_horizontal_plan(&self) -> Plan {
        Plan::FillPositive
    }
    fn get_vertical_plan(&self) -> Plan {
        Plan::FillPositive
    }
    fn replan(&self) {
        // Cascade replan signal to all children
        for el in self.childs.borrow().values() {
            el.drawable.replan();
        }
    }

    fn try_to_interactive(&self) -> Option<&dyn Interactive> {
        Some(self)
    }

    fn is_enabled(&self) -> bool {
        *self.enabled.borrow()
    }

    fn set_enabled(&self, e: bool) {
        *self.enabled.borrow_mut() = e
    }

    fn as_drawable(&self) -> &dyn Drawable {
        self
    }
}

impl Layout for EdgeLayout {
    fn measure_child(&self, c: &dyn Drawable, canvas: &Canvas) -> Rect {
        if !c.is_enabled() {
            return Rect::new(0.0, 0.0, 0.0, 0.0);
        }

        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)
        }
    }

    fn as_layout(&self) -> &dyn Layout {
        self
    }

    fn foreach_child(&self, f: &mut dyn FnMut(&dyn Drawable)) {
        let ch = self.childs.borrow();
        for i in ch.iter() {
            f(i.1.drawable.as_ref());
        }
    }
}

impl Interactive for EdgeLayout {
    fn handle_and_route(&self, e: &Event, u: &dyn Any) {
        self.foreach_child(&mut |x| {
            // Check if the child is enabled before attempting to route events
            if x.is_enabled()
                && let Some(i) = x.try_to_interactive()
            {
                i.handle_and_route(e, u);
            }
        });
    }
}

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

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

        self
    }

    pub fn add_child_inplace(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")
    }
}