yuno 0.1.0

A declarative UI layout and rendering framework powered by Skia.
#[allow(deprecated)]
use skia_safe::gradient_shader::GradientShaderColors;

#[allow(deprecated)]
use skia_safe::{Canvas, Color, Color4f, Paint, Point, RRect, Rect, TileMode, gradient_shader};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::drawing::{Drawable, Plan};
use crate::layouts::{HasPlanner, Layout};

pub struct AudioSpectrumChart {
    pub v: RefCell<HashMap<u64, [f32; 128]>>,
    pub current_time: RefCell<i64>,

    colors: [Color; 3],
    cap_paint: Paint,

    planner: RefCell<Option<Rc<dyn Layout>>>,
}

impl HasPlanner for AudioSpectrumChart {
    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 AudioSpectrumChart {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        let area = self.get_planned_drawing_area(canvas);
        let available_w = area.width();
        let available_h = area.height();

        if available_w <= 0.0 || available_h <= 0.0 {
            return Ok(());
        }

        let empty_frame = [0.0f32; 128];
        let frame_buffer;
        let mut current_frame: Option<&[f32; 128]> = None;

        let current_time = *self.current_time.borrow();
        let v = self.v.borrow();

        // Audio frame lookup
        if current_time >= 0 {
            let time_u64 = current_time as u64;
            for offset in 0..=42 {
                if let Some(frame) = v.get(&time_u64.saturating_sub(offset)) {
                    frame_buffer = *frame;
                    current_frame = Some(&frame_buffer);
                    break;
                }
                if let Some(frame) = v.get(&(time_u64 + offset)) {
                    frame_buffer = *frame;
                    current_frame = Some(&frame_buffer);
                    break;
                }
            }
        }

        let frame_data = current_frame.unwrap_or(&empty_frame);

        // Dimensional math based strictly on absolute area
        let max_bar_h = available_h / 2.0;
        let center_y = area.top + available_h / 2.0;
        let total_bars = 128;
        let step_x = available_w / total_bars as f32;
        let bar_width = (step_x * 0.7).max(1.0);
        let visual_scale = 3.5;

        // Dynamically build gradient shader based on the current absolute bounding box
        #[allow(deprecated)]
        let shader = gradient_shader::linear(
            (
                Point::new(area.left, area.top),
                Point::new(area.right, area.top),
            ),
            GradientShaderColors::Colors(&self.colors),
            None,
            TileMode::Clamp,
            None,
            None,
        );

        let mut bar_paint = Paint::default();
        bar_paint.set_anti_alias(true);
        if let Some(s) = &shader {
            bar_paint.set_shader(s.clone());
        } else {
            bar_paint.set_color4f(Color4f::new(0.26, 0.52, 0.96, 1.0), None);
        }

        let mut reflection_paint = bar_paint.clone();
        reflection_paint.set_alpha_f(0.25);

        // Rendering loop
        for (i, &value) in frame_data.iter().enumerate() {
            let boost = 1.0 + (i as f32 / total_bars as f32) * 1.5;
            let amplitude = (value * visual_scale * boost).clamp(1.0, max_bar_h);

            // Absolute X coordinate
            let x = area.left + (i as f32) * step_x;

            let top_rect = Rect::from_xywh(x, center_y - amplitude, bar_width, amplitude);
            let top_rrect = RRect::new_rect_xy(top_rect, bar_width / 2.0, bar_width / 2.0);
            canvas.draw_rrect(top_rrect, &bar_paint);

            let bottom_rect = Rect::from_xywh(x, center_y, bar_width, amplitude * 0.6);
            let bottom_rrect = RRect::new_rect_xy(bottom_rect, bar_width / 2.0, bar_width / 2.0);
            canvas.draw_rrect(bottom_rrect, &reflection_paint);

            if amplitude > 2.0 {
                canvas.draw_circle(
                    Point::new(x + bar_width / 2.0, center_y - amplitude - 2.0),
                    bar_width / 2.0,
                    &self.cap_paint,
                );
            }
        }

        Ok(())
    }

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

impl AudioSpectrumChart {
    pub fn new(v: HashMap<u64, [f32; 128]>) -> Rc<Self> {
        let mut cap_paint = Paint::default();
        cap_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(1.0, 1.0, 1.0, 0.9), None);

        let colors = [
            Color::from_argb(255, 255, 42, 133),
            Color::from_argb(255, 138, 43, 226),
            Color::from_argb(255, 0, 212, 255),
        ];

        Rc::new(Self {
            v: RefCell::new(v),
            current_time: RefCell::new(0),
            colors,
            cap_paint,
            planner: RefCell::new(None),
        })
    }
}