armas_icon/lib.rs
1//! Generic Icon System for egui
2//!
3//! Provides a generic SVG-based icon system with:
4//! - Static icon data via [`IconData`]
5//! - Runtime SVG parsing via the `runtime` feature
6//! - Rendering with dynamic colors and sizes
7//!
8//! # Architecture
9//!
10//! - [`IconData`] - Pre-tessellated icon geometry (static references)
11//! - [`OwnedIconData`] - Runtime-parsed icon geometry (owned buffers)
12//! - [`Icon`] - Generic icon widget (works with both)
13//! - [`render_icon`] / [`render_icon_data`] - Low-level rendering
14//!
15//! # Example
16//!
17//! ```rust,no_run
18//! use armas_icon::{Icon, IconData};
19//! use egui::{Color32, Ui};
20//!
21//! static MY_ICON: IconData = IconData {
22//! name: "my_icon",
23//! vertices: &[(0.0, 0.0), (24.0, 12.0), (0.0, 24.0)],
24//! indices: &[0, 1, 2],
25//! viewbox_width: 24.0,
26//! viewbox_height: 24.0,
27//! };
28//!
29//! fn show_icon(ui: &mut Ui) {
30//! Icon::new(&MY_ICON)
31//! .size(24.0)
32//! .color(Color32::WHITE)
33//! .show(ui);
34//! }
35//! ```
36
37#[cfg(feature = "runtime")]
38mod tessellate;
39
40#[cfg(feature = "runtime")]
41pub mod runtime;
42
43use egui::{epaint::Vertex, Color32, Mesh, Painter, Pos2, Rect, Response, Sense, Ui, Vec2};
44
45/// Pre-tessellated icon data
46///
47/// Contains the geometry data for rendering an icon. This is typically
48/// generated at compile time from SVG files using the build script.
49#[derive(Debug, Clone)]
50pub struct IconData {
51 /// Icon name (for debugging)
52 pub name: &'static str,
53 /// Vertex positions as (x, y) tuples
54 pub vertices: &'static [(f32, f32)],
55 /// Triangle indices
56 pub indices: &'static [u32],
57 /// Original viewbox width
58 pub viewbox_width: f32,
59 /// Original viewbox height
60 pub viewbox_height: f32,
61}
62
63/// Icon data that owns its buffers.
64///
65/// Runtime counterpart to [`IconData`]. Use this when icon geometry is
66/// produced at runtime (e.g. from [`runtime::parse_svg`]).
67#[derive(Debug, Clone)]
68pub struct OwnedIconData {
69 /// Icon name
70 pub name: String,
71 /// Vertex positions as (x, y) tuples
72 pub vertices: Vec<(f32, f32)>,
73 /// Triangle indices
74 pub indices: Vec<u32>,
75 /// Original viewbox width
76 pub viewbox_width: f32,
77 /// Original viewbox height
78 pub viewbox_height: f32,
79}
80
81/// Render icon geometry to an egui painter.
82///
83/// Low-level function that takes raw vertex/index slices. Both
84/// [`render_icon`] and [`OwnedIconData::render`] delegate to this.
85pub fn render_icon_data(
86 painter: &Painter,
87 rect: Rect,
88 vertices: &[(f32, f32)],
89 indices: &[u32],
90 viewbox_width: f32,
91 viewbox_height: f32,
92 color: Color32,
93) {
94 let scale_x = rect.width() / viewbox_width;
95 let scale_y = rect.height() / viewbox_height;
96 let scale = scale_x.min(scale_y);
97
98 let offset_x = rect.left() + viewbox_width.mul_add(-scale, rect.width()) / 2.0;
99 let offset_y = rect.top() + viewbox_height.mul_add(-scale, rect.height()) / 2.0;
100
101 let mut mesh = Mesh::default();
102
103 for &(x, y) in vertices {
104 let pos = Pos2::new(offset_x + x * scale, offset_y + y * scale);
105 mesh.vertices.push(Vertex {
106 pos,
107 uv: Pos2::ZERO,
108 color,
109 });
110 }
111
112 mesh.indices.extend_from_slice(indices);
113
114 painter.add(mesh);
115}
116
117/// Render icon data to an egui painter.
118///
119/// Transforms and renders the pre-tessellated icon geometry to fit
120/// within the given rectangle, maintaining aspect ratio and centering.
121pub fn render_icon(painter: &Painter, rect: Rect, icon_data: &IconData, color: Color32) {
122 render_icon_data(
123 painter,
124 rect,
125 icon_data.vertices,
126 icon_data.indices,
127 icon_data.viewbox_width,
128 icon_data.viewbox_height,
129 color,
130 );
131}
132
133impl OwnedIconData {
134 /// Render this icon to an egui painter.
135 pub fn render(&self, painter: &Painter, rect: Rect, color: Color32) {
136 render_icon_data(
137 painter,
138 rect,
139 &self.vertices,
140 &self.indices,
141 self.viewbox_width,
142 self.viewbox_height,
143 color,
144 );
145 }
146}
147
148/// Generic icon widget
149///
150/// Renders any [`IconData`] with configurable size and color.
151///
152/// # Example
153///
154/// ```rust,no_run
155/// # use armas_icon::{Icon, IconData};
156/// # use egui::{Color32, Ui};
157/// # static MY_ICON: IconData = IconData {
158/// # name: "test", vertices: &[], indices: &[],
159/// # viewbox_width: 24.0, viewbox_height: 24.0,
160/// # };
161/// # fn example(ui: &mut Ui) {
162/// Icon::new(&MY_ICON)
163/// .size(32.0)
164/// .color(Color32::RED)
165/// .show(ui);
166/// # }
167/// ```
168pub struct Icon<'a> {
169 vertices: &'a [(f32, f32)],
170 indices: &'a [u32],
171 viewbox_width: f32,
172 viewbox_height: f32,
173 size: f32,
174 color: Color32,
175}
176
177impl<'a> Icon<'a> {
178 /// Create a new icon widget from static [`IconData`].
179 #[must_use]
180 pub const fn new(icon_data: &'a IconData) -> Self {
181 Self {
182 vertices: icon_data.vertices,
183 indices: icon_data.indices,
184 viewbox_width: icon_data.viewbox_width,
185 viewbox_height: icon_data.viewbox_height,
186 size: 24.0,
187 color: Color32::WHITE,
188 }
189 }
190
191 /// Create a new icon widget from [`OwnedIconData`].
192 #[must_use]
193 pub fn from_owned(data: &'a OwnedIconData) -> Self {
194 Self {
195 vertices: &data.vertices,
196 indices: &data.indices,
197 viewbox_width: data.viewbox_width,
198 viewbox_height: data.viewbox_height,
199 size: 24.0,
200 color: Color32::WHITE,
201 }
202 }
203
204 /// Set the icon size (width and height will be equal)
205 #[must_use]
206 pub const fn size(mut self, size: f32) -> Self {
207 self.size = size;
208 self
209 }
210
211 /// Set the icon color
212 #[must_use]
213 pub const fn color(mut self, color: Color32) -> Self {
214 self.color = color;
215 self
216 }
217
218 /// Show the icon
219 pub fn show(self, ui: &mut Ui) -> Response {
220 let (rect, response) = ui.allocate_exact_size(Vec2::splat(self.size), Sense::click());
221
222 if ui.is_rect_visible(rect) {
223 if self.vertices.is_empty() {
224 ui.painter().rect_filled(rect, 2.0, Color32::from_gray(100));
225 } else {
226 render_icon_data(
227 ui.painter(),
228 rect,
229 self.vertices,
230 self.indices,
231 self.viewbox_width,
232 self.viewbox_height,
233 self.color,
234 );
235 }
236 }
237
238 response
239 }
240}