Skip to main content

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}