use std::collections::HashMap;
use std::sync::OnceLock;
use eframe::egui::{self, ColorImage, TextureHandle};
use crate::core::IconInfo;
include!(concat!(env!("OUT_DIR"), "/embedded_icons.rs"));
static ICONS: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
fn get_icons() -> &'static HashMap<&'static str, &'static str> {
ICONS.get_or_init(|| get_embedded_icons())
}
pub struct IconCache {
textures: HashMap<String, TextureHandle>,
}
impl IconCache {
pub fn new() -> Self {
Self {
textures: HashMap::new(),
}
}
pub fn get_or_load(&mut self, ctx: &egui::Context, icon_name: &str) -> Option<TextureHandle> {
if let Some(texture) = self.textures.get(icon_name) {
return Some(texture.clone());
}
let icons = get_icons();
if let Some(svg_data) = icons.get(icon_name) {
if let Some(image) = render_svg_to_image(svg_data, 32, 32) {
let texture = ctx.load_texture(
icon_name,
image,
egui::TextureOptions::LINEAR,
);
self.textures.insert(icon_name.to_string(), texture.clone());
return Some(texture);
}
}
None
}
pub fn clear(&mut self) {
self.textures.clear();
}
pub fn len(&self) -> usize {
self.textures.len()
}
pub fn is_empty(&self) -> bool {
self.textures.is_empty()
}
}
impl Default for IconCache {
fn default() -> Self {
Self::new()
}
}
pub fn render_svg_to_image(svg_data: &str, width: u32, height: u32) -> Option<ColorImage> {
let opts = resvg::usvg::Options::default();
let tree = resvg::usvg::Tree::from_str(svg_data, &opts).ok()?;
let size = tree.size();
let scale_x = width as f32 / size.width();
let scale_y = height as f32 / size.height();
let scale = scale_x.min(scale_y);
let scaled_width = (size.width() * scale) as u32;
let scaled_height = (size.height() * scale) as u32;
let mut pixmap = resvg::tiny_skia::Pixmap::new(scaled_width, scaled_height)?;
let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
resvg::render(&tree, transform, &mut pixmap.as_mut());
let pixels: Vec<egui::Color32> = pixmap
.pixels()
.iter()
.map(|p| egui::Color32::from_rgba_premultiplied(p.red(), p.green(), p.blue(), p.alpha()))
.collect();
Some(ColorImage {
size: [scaled_width as usize, scaled_height as usize],
pixels,
})
}
pub fn load_available_icons() -> Vec<IconInfo> {
let icons_map = get_icons();
let mut icons: Vec<IconInfo> = icons_map
.keys()
.map(|name| {
let filename = format!("{}-original.svg", name);
IconInfo::new(name.to_string(), filename)
})
.collect();
icons.sort_by(|a, b| a.name.cmp(&b.name));
icons
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_icon_cache_creation() {
let cache = IconCache::new();
assert!(cache.is_empty());
}
#[test]
fn test_render_invalid_svg() {
let result = render_svg_to_image("invalid svg content", 32, 32);
assert!(result.is_none());
}
}