gled-ui 2.30.1

gled's egui interface, shared by the desktop app and the browser
Documentation
//! The SVG preview the user interface draws. The lamp layout is read on the
//! client by `crate::layout`.

use egui::{Context, TextureHandle};
use std::sync::Arc;

use gled_model::asset::svg::Svg;

/// The preview image of the project's SVG, rendered again only when the
/// contents change. The lamp positions are the engine's business
/// (`crate::layout`); this only draws.
#[derive(Default)]
pub(crate) struct SvgRuntime {
    rendered: Option<Arc<str>>,
    texture: Option<TextureHandle>,
}

impl std::fmt::Debug for SvgRuntime {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SvgRuntime")
            .field("rendered", &self.rendered.is_some())
            .finish_non_exhaustive()
    }
}

impl SvgRuntime {
    /// The preview of `svg`, rendering it first if its contents are new.
    pub(crate) fn texture(&mut self, svg: &Svg, context: &Context) -> Option<TextureHandle> {
        let contents = svg.contents();
        let current = self
            .rendered
            .as_ref()
            .is_some_and(|rendered| Arc::ptr_eq(rendered, contents) || rendered == contents);
        if !current {
            self.rendered = Some(contents.clone());
            self.texture = render_preview(contents, context);
        }
        self.texture.clone()
    }

    /// Forgets the rendered preview, e.g. when the project is closed or
    /// replaced.
    pub(crate) fn reset(&mut self) {
        self.rendered = None;
        self.texture = None;
    }
}

fn render_preview(contents: &str, context: &Context) -> Option<TextureHandle> {
    let svg = gled_svg::ParsedSvg::parse(contents).ok()?;
    let rendered = svg.render().ok()?;
    let image = egui::ColorImage::from_rgba_unmultiplied([rendered.size; 2], &rendered.rgba);
    Some(context.load_texture("svg_preview", image, egui::TextureOptions::default()))
}

/// Reads an SVG file.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn load_svg(path: &std::path::Path) -> std::io::Result<Svg> {
    Ok(Svg::new(std::fs::read_to_string(path)?))
}

/// Writes the SVG to a file.
#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn save_svg(svg: &Svg, path: &std::path::Path) -> std::io::Result<()> {
    std::fs::write(path, svg.contents().as_bytes())
}