gled 2.6.2

gled is an application for creating animations and effects on artnet or wled light installations
use crate::{
    pipeline::{
        extract_output::ExtractOutput, group::Group, preview_indices::PreviewIndices,
        texture_to_output::positions::Positions,
    },
    svg::{measurement_point::MeasurementPoints, universe_color_channels::UniverseColorChannels},
    ui::action::UiAction,
};
use anyhow::Result;
use egui::{Context, Rect, TextureHandle};
use serde::{Deserialize, Serialize};
use std::{cell::RefCell, collections::BTreeSet, path::Path};

thread_local! {
    static MEASUREMENT_POINTS: RefCell<MeasurementPoints> = const { RefCell::new(MeasurementPoints::new()) };
}

#[derive(Serialize, Deserialize)]
pub struct Svg {
    svg_contents: String,
    #[serde(skip)]
    image: Option<TextureHandle>,
}

impl std::fmt::Debug for Svg {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Svg")
            .field("svg_contents", &self.svg_contents)
            .finish()
    }
}

impl Clone for Svg {
    fn clone(&self) -> Self {
        Self {
            svg_contents: self.svg_contents.clone(),
            image: None,
        }
    }
}

impl Svg {
    pub fn load(path: &Path) -> Result<Self> {
        let svg_contents = std::fs::read_to_string(path)?;

        Ok(Self {
            svg_contents,
            image: None,
        })
    }

    pub fn save(&self, path: &Path) -> Result<()> {
        std::fs::write(path, &self.svg_contents)?;

        Ok(())
    }

    pub fn image(&mut self, context: &Context) -> Option<TextureHandle> {
        if self.image.is_none() {
            self.image = {
                let svg = crate::svg::ParsedSvg::parse(&self.svg_contents).ok()?;
                let measurement_points = MeasurementPoints::from(&svg);
                UniverseColorChannels::from(&svg).set();
                let universes = measurement_points.universes();
                MEASUREMENT_POINTS.with_borrow_mut(|mp| {
                    *mp = measurement_points;
                });
                let image = svg.render().ok()?;
                PreviewIndices::get().send_positions();
                *ExtractOutput::get().universes.lock() = universes;
                UiAction::SendPositions.enqueue();

                let texture_handle =
                    context.load_texture("svg_preview", image, egui::TextureOptions::default());

                Some(texture_handle)
            };
        }
        self.image.clone()
    }

    pub fn reset() {
        MEASUREMENT_POINTS.with_borrow_mut(|mp| {
            *mp = MeasurementPoints::new();
        });
    }

    pub fn groups() -> Vec<Group> {
        MEASUREMENT_POINTS.with_borrow(|mp| mp.groups())
    }

    pub fn positions(group: &Group) -> Positions {
        MEASUREMENT_POINTS.with_borrow(|mp| mp.positions(group))
    }

    pub fn preview_positions() -> Positions {
        MEASUREMENT_POINTS.with_borrow(|mp| mp.preview_positions())
    }

    pub fn preview_uv() -> Option<Rect> {
        MEASUREMENT_POINTS.with_borrow_mut(|mp| mp.preview_uv())
    }

    pub fn universes() -> BTreeSet<u16> {
        MEASUREMENT_POINTS.with_borrow(|mp| mp.universes())
    }
}