iris_hub/services/
icon_service.rs1use std::collections::HashMap;
13use std::sync::OnceLock;
14use eframe::egui::{self, ColorImage, TextureHandle};
15
16use crate::core::IconInfo;
17
18include!(concat!(env!("OUT_DIR"), "/embedded_icons.rs"));
20
21static ICONS: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
23
24fn get_icons() -> &'static HashMap<&'static str, &'static str> {
25 ICONS.get_or_init(|| get_embedded_icons())
26}
27
28pub struct IconCache {
33 textures: HashMap<String, TextureHandle>,
35}
36
37impl IconCache {
38 pub fn new() -> Self {
40 Self {
41 textures: HashMap::new(),
42 }
43 }
44
45 pub fn get_or_load(&mut self, ctx: &egui::Context, icon_name: &str) -> Option<TextureHandle> {
58 if let Some(texture) = self.textures.get(icon_name) {
60 return Some(texture.clone());
61 }
62
63 let icons = get_icons();
65 if let Some(svg_data) = icons.get(icon_name) {
66 if let Some(image) = render_svg_to_image(svg_data, 32, 32) {
67 let texture = ctx.load_texture(
68 icon_name,
69 image,
70 egui::TextureOptions::LINEAR,
71 );
72 self.textures.insert(icon_name.to_string(), texture.clone());
73 return Some(texture);
74 }
75 }
76
77 None
78 }
79
80 pub fn clear(&mut self) {
82 self.textures.clear();
83 }
84
85 pub fn len(&self) -> usize {
87 self.textures.len()
88 }
89
90 pub fn is_empty(&self) -> bool {
92 self.textures.is_empty()
93 }
94}
95
96impl Default for IconCache {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101
102pub fn render_svg_to_image(svg_data: &str, width: u32, height: u32) -> Option<ColorImage> {
115 let opts = resvg::usvg::Options::default();
116 let tree = resvg::usvg::Tree::from_str(svg_data, &opts).ok()?;
117
118 let size = tree.size();
119 let scale_x = width as f32 / size.width();
120 let scale_y = height as f32 / size.height();
121 let scale = scale_x.min(scale_y);
122
123 let scaled_width = (size.width() * scale) as u32;
124 let scaled_height = (size.height() * scale) as u32;
125
126 let mut pixmap = resvg::tiny_skia::Pixmap::new(scaled_width, scaled_height)?;
127
128 let transform = resvg::tiny_skia::Transform::from_scale(scale, scale);
129 resvg::render(&tree, transform, &mut pixmap.as_mut());
130
131 let pixels: Vec<egui::Color32> = pixmap
132 .pixels()
133 .iter()
134 .map(|p| egui::Color32::from_rgba_premultiplied(p.red(), p.green(), p.blue(), p.alpha()))
135 .collect();
136
137 Some(ColorImage {
138 size: [scaled_width as usize, scaled_height as usize],
139 pixels,
140 })
141}
142
143pub fn load_available_icons() -> Vec<IconInfo> {
159 let icons_map = get_icons();
160 let mut icons: Vec<IconInfo> = icons_map
161 .keys()
162 .map(|name| {
163 let filename = format!("{}-original.svg", name);
164 IconInfo::new(name.to_string(), filename)
165 })
166 .collect();
167
168 icons.sort_by(|a, b| a.name.cmp(&b.name));
169 icons
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_icon_cache_creation() {
178 let cache = IconCache::new();
179 assert!(cache.is_empty());
180 }
181
182 #[test]
183 fn test_render_invalid_svg() {
184 let result = render_svg_to_image("invalid svg content", 32, 32);
185 assert!(result.is_none());
186 }
187}