yuno 0.1.0

A declarative UI layout and rendering framework powered by Skia.
pub mod audio_spectrum_chart;

use crate::controls::audio_visualization::audio_spectrum_chart::AudioSpectrumChart;
use crate::drawing::{Drawable, Plan, RoundedRectangle};
use crate::layouts::{EdgeLayout, EdgeLayoutElement, Edges, HasPlanner, Layout};
use crate::story_board::{StoryBoard, easing};
use skia_safe::{Canvas, Color4f, Paint, Point, Rect};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

pub struct AudioVisualization {
    sh_state: RefCell<StoryBoard>,
    w: u32,
    h: u32,

    planner: RefCell<Option<Rc<dyn Layout>>>,
    internal_layout: Rc<EdgeLayout>,
    spectrum_chart: Rc<AudioSpectrumChart>,
}

impl HasPlanner for AudioVisualization {
    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 AudioVisualization {
    fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
        // Transparently pass the planned area to the internal layout
        self.get_planned_drawing_area(canvas)
    }
}

impl Drawable for AudioVisualization {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        let slide_state = f64::from(&*self.sh_state.borrow()) as f32;
        let slide_percent = slide_state / 100.0;

        let panel_w = self.w as f32;
        let max_slide_offset = -(panel_w + 90.0);
        let anim_x = slide_percent * max_slide_offset;

        if anim_x <= max_slide_offset + 5.0 {
            return Ok(()); // Off-screen cull
        }

        canvas.save();
        // ONLY translate by the animation offset (delta).
        // Do not touch absolute positions, internal_layout handles that.
        canvas.translate(Point::new(anim_x, 0.0));

        self.internal_layout.draw(canvas)?;

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

    fn get_horizontal_plan(&self) -> Plan {
        Plan::Fit(self.w as f32)
    }
    fn get_vertical_plan(&self) -> Plan {
        Plan::Fit(self.h as f32)
    }
}

impl AudioVisualization {
    pub fn new(v: &HashMap<u64, [f32; 128]>, w: u32, h: u32) -> Rc<Self> {
        let internal_layout = EdgeLayout::new();

        // Setup background and border as standard sub-widgets
        let mut bg_paint = Paint::default();
        bg_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(0.08, 0.08, 0.1, 0.75), None);

        let mut border_paint = Paint::default();
        border_paint
            .set_anti_alias(true)
            .set_style(skia_safe::paint::Style::Stroke)
            .set_stroke_width(1.5)
            .set_color4f(Color4f::new(1.0, 1.0, 1.0, 0.12), None);

        let bg = RoundedRectangle::new(None, None, 12.0, 12.0, bg_paint);
        let border = RoundedRectangle::new(None, None, 12.0, 12.0, border_paint);

        let spectrum_chart = AudioSpectrumChart::new(v.clone());

        let add = |id: &str, dr: Rc<dyn Drawable>, l: f32, t: f32, r: f32, b: f32| {
            internal_layout.add_child(
                id,
                EdgeLayoutElement {
                    drawable: dr,
                    l_edge: Edges::Left,
                    l_margin: l,
                    t_edge: Edges::Top,
                    t_margin: t,
                    r_edge: Edges::Right,
                    r_margin: r,
                    b_edge: Edges::Bottom,
                    b_margin: b,
                },
            );
        };

        // Background and border fill the exact container bounds (margin 0)
        add("bg", bg, 0.0, 0.0, 0.0, 0.0);
        add("border", border, 0.0, 0.0, 0.0, 0.0);

        // The padding is now natively handled by Layout logic! (16px horizontal, 20px vertical)
        add("spectrum", spectrum_chart.clone(), 16.0, 20.0, 16.0, 20.0);

        let instance = Rc::new(Self {
            sh_state: RefCell::new(StoryBoard::init(
                100f64,
                0f64,
                500f64,
                false,
                easing::cubic_in_out,
            )),
            w,
            h,
            planner: RefCell::new(None),
            internal_layout: internal_layout.clone(),
            spectrum_chart,
        });

        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: 40.0,
            t_edge: Edges::Top,
            t_margin: 91.0,
            r_edge: Edges::Left,
            r_margin: 40.0 + self.w as f32,
            b_edge: Edges::Top,
            b_margin: 91.0 + self.h as f32,
        }
    }

    pub fn set_data(&self, v: &HashMap<u64, [f32; 128]>) {
        *self.spectrum_chart.v.borrow_mut() = v.clone();
    }

    pub fn set_current_time(&self, t: i64) {
        *self.spectrum_chart.current_time.borrow_mut() = t;
    }

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