yuno 0.2.1

Multimedia UI layout and rendering framework powered by Skia.
pub mod linechart;
pub mod ringchart;

use crate::drawing::sprites::{HorizontalAlign, Text};
use crate::drawing::{Drawable, Plan, RoundedRectangle};
use crate::interacting::{Event, Interactive};
use crate::layouts::{EdgeLayout, EdgeLayoutElement, Edges, HasPlanner, Layout};
use crate::sys_monitor::HwStats;
use linechart::LineChart;
use ringchart::RingProgressChart;
use skia_safe::{Canvas, Color, Color4f, Font, Paint, PaintStyle, Rect};
use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;

/// Represents basic video information decoupled from the media pipeline.
pub struct VideoMetadata {
    pub video_filename: String,
    pub codec_name: String,
    pub frame_rate: f64,
    pub sv_sz: (i32, i32),
}

pub struct TopBar {
    planner: RefCell<Option<Rc<dyn Layout>>>,

    // The internal layout engine
    internal_layout: Rc<EdgeLayout>,

    // Child Components kept for dynamic updates
    video_title: Rc<Text>,
    video_subtitle: Rc<Text>,

    cpu_chart: Rc<LineChart>,
    gpu_chart: Rc<LineChart>,
    vram_ring: Rc<RingProgressChart>,
    ram_ring: Rc<RingProgressChart>,

    enabled: RefCell<bool>,
}

impl HasPlanner for TopBar {
    fn planner(&self) -> &RefCell<Option<Rc<dyn Layout>>> {
        &self.planner
    }
    fn set_planner(&self, p: Option<Rc<dyn Layout>>) {
        *self.planner.borrow_mut() = p;
    }
}

// Enable TopBar to act as a Planner for its internal EdgeLayout
impl Layout for TopBar {
    fn measure_child(&self, _c: &dyn Drawable, canvas: &Canvas) -> Rect {
        // Just forward the exact planned area TopBar was assigned to its internal layout
        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.video_title.as_ref());
        f(self.video_subtitle.as_ref());

        f(self.cpu_chart.as_ref());
        f(self.gpu_chart.as_ref());

        f(self.vram_ring.as_ref());
        f(self.ram_ring.as_ref());
    }
}

impl Interactive for TopBar {
    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 TopBar {
    fn draw(&self, canvas: &Canvas) -> anyhow::Result<()> {
        self.internal_layout.draw(canvas)?;
        Ok(())
    }

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

    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 TopBar {
    pub fn new(font: &Font, meta: &VideoMetadata, hw: &HwStats) -> Rc<Self> {
        // 1. Setup Paints
        let mut bg_paint = Paint::default();
        bg_paint
            .set_anti_alias(true)
            .set_color4f(Color4f::new(0.08, 0.08, 0.1, 0.85), None);

        let mut border_paint = Paint::default();
        border_paint
            .set_anti_alias(true)
            .set_style(PaintStyle::Stroke)
            .set_stroke_width(1.0)
            .set_color4f(Color4f::new(1.0, 1.0, 1.0, 0.15), None);

        let mut text_paint = Paint::default();
        text_paint.set_anti_alias(true).set_color(Color::WHITE);

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

        let mut sub_font = font.clone();
        sub_font.set_size(14.0);

        // 2. Initialize Sub-Components
        let bg =
            RoundedRectangle::new(Plan::FillPositive, Plan::FillPositive, 12.0, 12.0, bg_paint);
        let border = RoundedRectangle::new(
            Plan::FillPositive,
            Plan::FillPositive,
            12.0,
            12.0,
            border_paint,
        );

        let video_title = Text::new(
            &meta.video_filename,
            font.clone(),
            text_paint.clone(),
            HorizontalAlign::Left,
        );

        let sub_info = format!(
            "Codec: {}  |  FPS: {:.2}  |  Size: {}x{}",
            meta.codec_name, meta.frame_rate, meta.sv_sz.0, meta.sv_sz.1
        );
        let video_subtitle = Text::new(
            &sub_info,
            sub_font.clone(),
            subtext_paint,
            HorizontalAlign::Left,
        );

        let gpu_colors = [
            Color::from_argb(255, 255, 42, 133),
            Color::from_argb(255, 138, 43, 226),
        ];
        let gpu_chart = LineChart::new(hw.gpu_history.clone(), gpu_colors);

        let cpu_colors = [
            Color::from_argb(255, 0, 212, 255),
            Color::from_argb(255, 41, 121, 255),
        ];
        let cpu_chart = LineChart::new(hw.cpu_history.clone(), cpu_colors);

        let vram_ring = RingProgressChart::new(
            hw.vram_usage,
            "VR",
            Color::from_rgb(138, 43, 226),
            font.clone(),
        );
        let ram_ring = RingProgressChart::new(
            hw.mem_usage,
            "RAM",
            Color::from_rgb(0, 212, 255),
            font.clone(),
        );

        let gpu_label = Text::new(
            "GPU",
            sub_font.clone(),
            text_paint.clone(),
            HorizontalAlign::Left,
        );
        let cpu_label = Text::new(
            "CPU",
            sub_font.clone(),
            text_paint.clone(),
            HorizontalAlign::Left,
        );

        // 3. Assemble Layout with Chain Calls
        let chart_y_margin = (55.0 - 32.0) / 2.0;

        // Create the initial layout instance as Rc to start the chain
        let internal_layout = EdgeLayout::new(true);

        let internal_layout = internal_layout
            .add_child(
                "bg",
                EdgeLayoutElement {
                    drawable: bg,
                    l_edge: Edges::Left,
                    l_margin: 0.0,
                    t_edge: Edges::Top,
                    t_margin: 0.0,
                    r_edge: Edges::Right,
                    r_margin: 0.0,
                    b_edge: Edges::Bottom,
                    b_margin: 0.0,
                },
            )
            .add_child(
                "border",
                EdgeLayoutElement {
                    drawable: border,
                    l_edge: Edges::Left,
                    l_margin: 0.0,
                    t_edge: Edges::Top,
                    t_margin: 0.0,
                    r_edge: Edges::Right,
                    r_margin: 0.0,
                    b_edge: Edges::Bottom,
                    b_margin: 0.0,
                },
            )
            .add_child(
                "title",
                EdgeLayoutElement {
                    drawable: video_title.clone(),
                    l_edge: Edges::Left,
                    l_margin: 24.0,
                    t_edge: Edges::Top,
                    t_margin: 14.0,
                    r_edge: Edges::Right,
                    r_margin: 300.0,
                    b_edge: Edges::Bottom,
                    b_margin: 20.0,
                },
            )
            .add_child(
                "subtitle",
                EdgeLayoutElement {
                    drawable: video_subtitle.clone(),
                    l_edge: Edges::Left,
                    l_margin: 24.0,
                    t_edge: Edges::Top,
                    t_margin: 34.0,
                    r_edge: Edges::Right,
                    r_margin: 300.0,
                    b_edge: Edges::Bottom,
                    b_margin: 0.0,
                },
            )
            .add_child(
                "gpu_chart",
                EdgeLayoutElement {
                    drawable: gpu_chart.clone(),
                    l_edge: Edges::Right,
                    l_margin: 114.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 24.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            )
            .add_child(
                "gpu_label",
                EdgeLayoutElement {
                    drawable: gpu_label,
                    l_edge: Edges::Right,
                    l_margin: 149.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 119.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            )
            .add_child(
                "cpu_chart",
                EdgeLayoutElement {
                    drawable: cpu_chart.clone(),
                    l_edge: Edges::Right,
                    l_margin: 254.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 164.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            )
            .add_child(
                "cpu_label",
                EdgeLayoutElement {
                    drawable: cpu_label,
                    l_edge: Edges::Right,
                    l_margin: 289.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 259.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            )
            .add_child(
                "vram_ring",
                EdgeLayoutElement {
                    drawable: vram_ring.clone(),
                    l_edge: Edges::Right,
                    l_margin: 326.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 294.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            )
            .add_child(
                "ram_ring",
                EdgeLayoutElement {
                    drawable: ram_ring.clone(),
                    l_edge: Edges::Right,
                    l_margin: 368.0,
                    t_edge: Edges::Top,
                    t_margin: chart_y_margin,
                    r_edge: Edges::Right,
                    r_margin: 336.0,
                    b_edge: Edges::Bottom,
                    b_margin: chart_y_margin,
                },
            );

        // 4. Construct Parent Instance
        let instance = Rc::new(Self {
            planner: RefCell::new(None),
            internal_layout: internal_layout.clone(),
            video_title,
            video_subtitle,
            cpu_chart,
            gpu_chart,
            vram_ring,
            ram_ring,
            enabled: RefCell::new(true),
        });

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

        instance
    }

    pub fn set_state(&self, meta: &VideoMetadata, hw: &HwStats, fps: f32) {
        // Direct mutations via RefCells held in the components
        self.video_title.set_text(&meta.video_filename);
        let sub_info = format!(
            "Codec: {}  |  FPS: {:.2}  |  Size: {}x{}  |  Rendering FPS: {:.2}",
            meta.codec_name, meta.frame_rate, meta.sv_sz.0, meta.sv_sz.1, fps
        );
        self.video_subtitle.set_text(&sub_info);

        *self.cpu_chart.history.borrow_mut() = hw.cpu_history.clone();
        *self.gpu_chart.history.borrow_mut() = hw.gpu_history.clone();
        *self.vram_ring.pct.borrow_mut() = hw.vram_usage;
        *self.ram_ring.pct.borrow_mut() = hw.mem_usage;
    }
}