yuno 0.2.0

Multimedia UI layout and rendering framework powered by Skia.
pub mod gradient_backdrop;
pub mod play_pause_icon;
pub mod time_text;
pub mod video_progress_bar;

use crate::drawing::{Drawable, Plan};
use crate::interacting::{Event, Interactive};
use crate::layouts::{EdgeLayout, EdgeLayoutElement, Edges, HasPlanner, Layout};
use crate::story_board::{StoryBoard, easing};
use skia_safe::{Canvas, Color, Color4f, Font, Paint, PaintStyle, PathBuilder, Point, Rect};
use std::any::Any;
use std::cell::RefCell;
use std::f64::consts::PI;
use std::rc::Rc;

use gradient_backdrop::GradientBackdrop;
use play_pause_icon::PlayPauseIcon;
use time_text::TimeText;
use video_progress_bar::VideoProgressBar;

pub struct PlayBar {
    sh_state: RefCell<StoryBoard>,
    internal_layout: Rc<EdgeLayout>,

    // Components saved for status updates
    icon: Rc<PlayPauseIcon>,
    progress: Rc<VideoProgressBar>,
    text: Rc<TimeText>,
    planner: RefCell<Option<Rc<dyn Layout>>>,

    enabled: RefCell<bool>,
}

impl HasPlanner for PlayBar {
    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 Layout for PlayBar {
    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.internal_layout.as_ref());
        f(self.icon.as_ref());
        f(self.progress.as_ref());
        f(self.text.as_ref());
    }
}

impl Interactive for PlayBar {
    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 Drawable for PlayBar {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        let slide_offset = f64::from(&*self.sh_state.borrow()) as f32;

        canvas.save();
        canvas.translate(Point::new(0.0, slide_offset));

        self.internal_layout.draw(canvas)?;

        canvas.restore();
        Ok(())
    }

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

    fn replan(&self) {
        self.foreach_child(&mut |x| x.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 PlayBar {
    pub fn new(current_time: i64, video_length: i64, font: &Font) -> Rc<Self> {
        // 1. Initialize Paints
        let mut ring_paint = Paint::default();
        ring_paint
            .set_anti_alias(true)
            .set_style(PaintStyle::Stroke)
            .set_stroke_width(3.0)
            .set_color4f(Color4f::new(0.26, 0.52, 0.96, 0.9), None);

        let mut bg_paint = Paint::default();
        bg_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(1.0, 1.0, 1.0, 0.2), None);

        let mut progress_paint = Paint::default();
        progress_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(0.26, 0.52, 0.96, 1.0), None);

        let mut thumb_paint = Paint::default();
        thumb_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(1.0, 1.0, 1.0, 1.0), None);

        let mut text_paint = Paint::default();
        text_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(0.95, 0.95, 0.95, 1.0), None);

        let mut shadow_paint = Paint::default();
        shadow_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(0.0, 0.0, 0.0, 0.6), None);

        let mut icon_paint = Paint::default();
        icon_paint.set_anti_alias(true);

        let size = 9.0;
        let mut builder = PathBuilder::new();
        builder
            .move_to(Point::new(-size * 0.3, -size * 0.9))
            .line_to(Point::new(size * 0.8, 0.0))
            .line_to(Point::new(-size * 0.3, size * 0.9));

        // 2. Component Construction
        let backdrop = Rc::new(GradientBackdrop {
            colors: [Color::from_argb(0, 0, 0, 0), Color::from_argb(199, 0, 0, 0)],
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
        });

        let icon = Rc::new(PlayPauseIcon {
            is_paused: RefCell::new(true),
            pp_state: RefCell::new(StoryBoard::init(
                0f64,
                1f64,
                200f64,
                false,
                easing::cubic_in_out,
            )),
            rotation_state: RefCell::new(StoryBoard::init(
                0f64,
                2f64 * PI,
                3000f64,
                true,
                easing::linear,
            )),
            play_path: builder.detach(),
            ring_paint,
            icon_paint,
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
        });

        let progress = Rc::new(VideoProgressBar {
            current_time: RefCell::new(current_time),
            video_length: RefCell::new(video_length),
            bg_paint,
            progress_paint,
            thumb_paint,
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
        });

        let text = Rc::new(TimeText {
            current_time: RefCell::new(current_time),
            video_length: RefCell::new(video_length),
            font: font.clone(),
            text_paint,
            shadow_paint,
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
        });

        // 3. Chain Layout Assembly
        let internal_layout = EdgeLayout::new(true)
            .add_child(
                "backdrop",
                EdgeLayoutElement {
                    drawable: backdrop.clone(),
                    l_edge: Edges::Left,
                    l_margin: 0.0,
                    r_edge: Edges::Right,
                    r_margin: 0.0,
                    t_edge: Edges::Bottom,
                    t_margin: 75.0,
                    b_edge: Edges::Bottom,
                    b_margin: 0.0,
                },
            )
            .add_child(
                "icon",
                EdgeLayoutElement {
                    drawable: icon.clone(),
                    l_edge: Edges::Left,
                    l_margin: 40.0,
                    r_edge: Edges::Left,
                    r_margin: 76.0,
                    t_edge: Edges::Bottom,
                    t_margin: 45.0,
                    b_edge: Edges::Bottom,
                    b_margin: 9.0,
                },
            )
            .add_child(
                "progress",
                EdgeLayoutElement {
                    drawable: progress.clone(),
                    l_edge: Edges::Left,
                    l_margin: 94.0,
                    r_edge: Edges::Right,
                    r_margin: 40.0,
                    t_edge: Edges::Bottom,
                    t_margin: 30.0,
                    b_edge: Edges::Bottom,
                    b_margin: 24.0,
                },
            )
            .add_child(
                "text",
                EdgeLayoutElement {
                    drawable: text.clone(),
                    l_edge: Edges::Left,
                    l_margin: 94.0,
                    r_edge: Edges::Right,
                    r_margin: 40.0,
                    t_edge: Edges::Bottom,
                    t_margin: 62.0,
                    b_edge: Edges::Bottom,
                    b_margin: 42.0,
                },
            );

        // 4. Instance Finalization
        let instance = Rc::new(Self {
            sh_state: RefCell::new(StoryBoard::init(
                100f64,
                0f64,
                500f64,
                false,
                easing::cubic_in_out,
            )),
            internal_layout: internal_layout.clone(),
            icon,
            progress,
            text,
            planner: RefCell::new(None),
            enabled: RefCell::new(true),
        });

        internal_layout.set_planner(Some(instance.clone() as Rc<dyn Layout>));
        instance
    }

    pub fn to_layout_element(self: &Rc<Self>) -> EdgeLayoutElement {
        EdgeLayoutElement {
            drawable: self.clone(),
            l_edge: Edges::Left,
            l_margin: 0.0,
            r_edge: Edges::Right,
            r_margin: 0.0,
            b_edge: Edges::Bottom,
            b_margin: 0.0,
            t_edge: Edges::Bottom,
            t_margin: 75.0,
        }
    }

    pub fn set_status(&self, is_paused: bool, current_time: i64, video_length: i64) {
        let mut is_paused_lock = self.icon.is_paused.borrow_mut();
        if *is_paused_lock != is_paused {
            self.icon.pp_state.borrow_mut().restart();
        }
        *is_paused_lock = is_paused;

        *self.progress.current_time.borrow_mut() = current_time;
        *self.progress.video_length.borrow_mut() = video_length;
        *self.text.current_time.borrow_mut() = current_time;
        *self.text.video_length.borrow_mut() = video_length;
    }

    pub fn toggle_sh(&self) {
        let mut sh = self.sh_state.borrow_mut();
        sh.reverse();
        sh.restart();
    }
}