yuno 0.2.1

Multimedia UI layout and rendering framework powered by Skia.
use crate::base::Property;
use crate::drawing::{Drawable, Plan};
use crate::interacting::{Event, Interactive};
use crate::layouts::{HasPlanner, Layout};
use crate::story_board::{StoryBoard, easing};
use skia_safe::{Canvas, Paint, Point, Rect};
use std::any::Any;
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::time::Duration;

pub struct SlideAnimation {
    status: Rc<Property<bool>>,
    direction: Point,
    pub state: RefCell<StoryBoard>,

    pub child: Rc<dyn Drawable>,
    pub planner: RefCell<Option<Rc<dyn Layout>>>,
    enabled: RefCell<bool>,
}

impl SlideAnimation {
    pub fn new(
        status: Rc<Property<bool>>,
        direction: Point,
        duration: Duration,
        child: Rc<dyn Drawable>,
    ) -> Rc<Self> {
        let mut state = StoryBoard::init(
            0.0,
            1.0,
            duration.as_millis() as f64,
            false,
            easing::cubic_in_out,
        );
        if status.get() {
            state.seek(1.0);
        }

        let s = Rc::new(Self {
            status: status.clone(),
            direction,
            child: child.clone(),
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
            state: RefCell::new(state),
        });

        child.set_planner(Some(s.clone()));

        // Use the raw pointer address of the struct as a unique listener ID
        let listener_id = format!("slide_anim_{:p}", s.as_ref());

        // Downgrade to Weak pointer to prevent circular reference memory leaks
        let weak_s = Rc::downgrade(&s);

        status.listen(
            &listener_id,
            Box::new(|v, u| {
                // Downcast to Weak, then attempt to upgrade to Strong
                if let Some(weak_u) = u.downcast_ref::<Weak<SlideAnimation>>()
                    && let Some(u) = weak_u.upgrade()
                {
                    if v {
                        u.state.borrow_mut().set_route(0.0, 1.0);
                    } else {
                        u.state.borrow_mut().set_route(1.0, 0.0);
                    }
                    u.state.borrow_mut().restart();
                }
            }),
            Box::new(weak_s),
        );

        s
    }
}

impl Drop for SlideAnimation {
    fn drop(&mut self) {
        let listener_id = format!("slide_anim_{:p}", self);
        self.status.hang_up(&listener_id);
    }
}

impl Layout for SlideAnimation {
    fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
        self.get_planned_drawing_area(canvas)
    }

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

    fn foreach_child(&self, f: &mut dyn FnMut(&dyn Drawable)) {
        f(self.child.as_ref())
    }
}

impl Drawable for SlideAnimation {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        let progress = f64::from(&*self.state.borrow()) as f32;

        // Perform off-screen culling if the animation is fully hidden
        if progress <= 0.001 {
            return Ok(());
        }

        let inv_progress = 1.0 - progress;
        let offset_x = self.direction.x * inv_progress;
        let offset_y = self.direction.y * inv_progress;

        canvas.save();

        if offset_x != 0.0 || offset_y != 0.0 {
            canvas.translate(Point::new(offset_x, offset_y));

            let mut layer_paint = Paint::default();
            layer_paint.set_alpha_f(progress);
            canvas.save_layer(&skia_safe::canvas::SaveLayerRec::default().paint(&layer_paint));
        }

        self.child.draw(canvas).ok();

        if offset_x != 0.0 || offset_y != 0.0 {
            canvas.restore();
        }

        canvas.restore();

        Ok(())
    }

    fn get_horizontal_plan(&self) -> Plan {
        self.child.get_horizontal_plan()
    }

    fn get_vertical_plan(&self) -> Plan {
        self.child.get_vertical_plan()
    }

    fn replan(&self) {
        self.child.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 HasPlanner for SlideAnimation {
    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 Interactive for SlideAnimation {
    fn handle_and_route(&self, e: &Event, u: &dyn Any) {
        self.foreach_child(&mut |x| {
            if x.is_enabled()
                && let Some(i) = x.try_to_interactive()
            {
                i.handle_and_route(e, u);
            }
        });
    }
}

pub struct SlideShowAnimation {
    pub state: RefCell<StoryBoard>,
    direction: Point,

    pub child: Rc<dyn Drawable>,
    pub planner: RefCell<Option<Rc<dyn Layout>>>,
    enabled: RefCell<bool>,
}

impl SlideShowAnimation {
    pub fn new(direction: Point, duration: Duration, child: Rc<dyn Drawable>) -> Rc<Self> {
        let mut state = StoryBoard::init(
            0.0,
            1.0,
            duration.as_millis() as f64,
            false,
            easing::cubic_fly_hover_out,
        );

        state.seek(1.0);

        let s = Rc::new(Self {
            direction,
            child: child.clone(),
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
            state: RefCell::new(state),
        });

        child.set_planner(Some(s.clone()));
        s
    }

    pub fn show(&self) {
        let mut state = self.state.borrow_mut();
        let p = state.get_progress();

        let t_in = 500.0 / 4500.0;
        let t_out = 4000.0 / 4500.0;

        if p > t_in && p < t_out {
            state.seek(t_in + 0.001);
        } else if p >= t_out {
            state.restart();
        }
    }
}

impl Layout for SlideShowAnimation {
    fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
        self.get_planned_drawing_area(canvas)
    }

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

    fn foreach_child(&self, f: &mut dyn FnMut(&dyn Drawable)) {
        f(self.child.as_ref())
    }
}

impl Drawable for SlideShowAnimation {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        let progress = f64::from(&*self.state.borrow()) as f32;

        if progress <= 0.001 {
            return Ok(());
        }

        let inv_progress = 1.0 - progress;
        let offset_x = self.direction.x * inv_progress;
        let offset_y = self.direction.y * inv_progress;

        canvas.save();

        if offset_x != 0.0 || offset_y != 0.0 {
            canvas.translate(Point::new(offset_x, offset_y));

            let mut layer_paint = Paint::default();
            layer_paint.set_alpha_f(progress);
            canvas.save_layer(&skia_safe::canvas::SaveLayerRec::default().paint(&layer_paint));
        }

        self.child.draw(canvas).ok();

        if offset_x != 0.0 || offset_y != 0.0 {
            canvas.restore();
        }

        canvas.restore();

        Ok(())
    }

    fn get_horizontal_plan(&self) -> Plan {
        self.child.get_horizontal_plan()
    }

    fn get_vertical_plan(&self) -> Plan {
        self.child.get_vertical_plan()
    }

    fn replan(&self) {
        self.child.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 HasPlanner for SlideShowAnimation {
    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 Interactive for SlideShowAnimation {
    fn handle_and_route(&self, e: &Event, u: &dyn Any) {
        self.foreach_child(&mut |x| {
            if x.is_enabled()
                && let Some(i) = x.try_to_interactive()
            {
                i.handle_and_route(e, u);
            }
        });
    }
}