Skip to main content

native_theme/model/
bundled.rs

1// Feature-gated bundled SVG icon access via include_bytes!
2//
3// With `material-icons` or `lucide-icons` features enabled, this module
4// embeds SVG files at compile time and makes them available via
5// `bundled_icon_svg()`. Without features, the function always returns None.
6
7use super::icons::{IconRole, IconSet};
8
9/// Returns the raw SVG bytes for a bundled icon, if the corresponding
10/// feature flag is enabled.
11///
12/// Returns `None` when:
13/// - The requested `IconSet` is not `Material` or `Lucide`
14/// - The feature flag for the requested set is not enabled
15///
16/// Returns `Some(&[u8])` containing valid SVG bytes when the feature
17/// is enabled. Callers typically convert to `IconData::Svg(bytes.to_vec())`.
18///
19/// # Examples
20///
21/// ```
22/// use native_theme::{IconSet, IconRole, bundled_icon_svg};
23///
24/// // Without features enabled, bundled icons return None
25/// let result = bundled_icon_svg(IconRole::ActionCopy, IconSet::SfSymbols);
26/// assert!(result.is_none());
27/// ```
28#[must_use = "this returns SVG bytes; it does not render the icon"]
29#[allow(unreachable_patterns, unused_variables)]
30pub fn bundled_icon_svg(role: IconRole, set: IconSet) -> Option<&'static [u8]> {
31    match set {
32        #[cfg(feature = "material-icons")]
33        IconSet::Material => material_svg(role),
34
35        #[cfg(feature = "lucide-icons")]
36        IconSet::Lucide => lucide_svg(role),
37
38        _ => None,
39    }
40}
41
42#[cfg(feature = "material-icons")]
43#[allow(unreachable_patterns)]
44fn material_svg(role: IconRole) -> Option<&'static [u8]> {
45    Some(match role {
46        // Dialog / Alert (6)
47        IconRole::DialogWarning => include_bytes!("../../icons/material/warning.svg"),
48        IconRole::DialogError => include_bytes!("../../icons/material/error.svg"),
49        IconRole::DialogInfo => include_bytes!("../../icons/material/info.svg"),
50        IconRole::DialogQuestion => include_bytes!("../../icons/material/help.svg"),
51        IconRole::DialogSuccess => include_bytes!("../../icons/material/check_circle.svg"),
52        IconRole::Shield => include_bytes!("../../icons/material/shield.svg"),
53
54        // Window Controls (4)
55        IconRole::WindowClose => include_bytes!("../../icons/material/close.svg"),
56        IconRole::WindowMinimize => include_bytes!("../../icons/material/minimize.svg"),
57        IconRole::WindowMaximize => include_bytes!("../../icons/material/open_in_full.svg"),
58        IconRole::WindowRestore => include_bytes!("../../icons/material/close_fullscreen.svg"),
59
60        // Common Actions (14)
61        IconRole::ActionSave => include_bytes!("../../icons/material/save.svg"),
62        IconRole::ActionDelete => include_bytes!("../../icons/material/delete.svg"),
63        IconRole::ActionCopy => include_bytes!("../../icons/material/content_copy.svg"),
64        IconRole::ActionPaste => include_bytes!("../../icons/material/content_paste.svg"),
65        IconRole::ActionCut => include_bytes!("../../icons/material/content_cut.svg"),
66        IconRole::ActionUndo => include_bytes!("../../icons/material/undo.svg"),
67        IconRole::ActionRedo => include_bytes!("../../icons/material/redo.svg"),
68        IconRole::ActionSearch => include_bytes!("../../icons/material/search.svg"),
69        IconRole::ActionSettings => include_bytes!("../../icons/material/settings.svg"),
70        IconRole::ActionEdit => include_bytes!("../../icons/material/edit.svg"),
71        IconRole::ActionAdd => include_bytes!("../../icons/material/add.svg"),
72        IconRole::ActionRemove => include_bytes!("../../icons/material/remove.svg"),
73        IconRole::ActionRefresh => include_bytes!("../../icons/material/refresh.svg"),
74        IconRole::ActionPrint => include_bytes!("../../icons/material/print.svg"),
75
76        // Navigation (6)
77        IconRole::NavBack => include_bytes!("../../icons/material/arrow_back.svg"),
78        IconRole::NavForward => include_bytes!("../../icons/material/arrow_forward.svg"),
79        IconRole::NavUp => include_bytes!("../../icons/material/arrow_upward.svg"),
80        IconRole::NavDown => include_bytes!("../../icons/material/arrow_downward.svg"),
81        IconRole::NavHome => include_bytes!("../../icons/material/home.svg"),
82        IconRole::NavMenu => include_bytes!("../../icons/material/menu.svg"),
83
84        // Files / Places (5)
85        IconRole::FileGeneric => include_bytes!("../../icons/material/description.svg"),
86        IconRole::FolderClosed => include_bytes!("../../icons/material/folder.svg"),
87        IconRole::FolderOpen => include_bytes!("../../icons/material/folder_open.svg"),
88        IconRole::TrashEmpty => include_bytes!("../../icons/material/delete.svg"),
89        IconRole::TrashFull => include_bytes!("../../icons/material/delete.svg"), // reuse delete
90
91        // Status (3)
92        IconRole::StatusBusy => include_bytes!("../../icons/material/progress_activity.svg"),
93        IconRole::StatusCheck => include_bytes!("../../icons/material/check.svg"),
94        IconRole::StatusError => include_bytes!("../../icons/material/error.svg"), // reuse error
95
96        // System (4)
97        IconRole::UserAccount => include_bytes!("../../icons/material/person.svg"),
98        IconRole::Notification => include_bytes!("../../icons/material/notifications.svg"),
99        IconRole::Help => include_bytes!("../../icons/material/help.svg"), // reuse help
100        IconRole::Lock => include_bytes!("../../icons/material/lock.svg"),
101
102        _ => return None, // #[non_exhaustive] forward compat
103    })
104}
105
106#[cfg(feature = "lucide-icons")]
107#[allow(unreachable_patterns)]
108fn lucide_svg(role: IconRole) -> Option<&'static [u8]> {
109    Some(match role {
110        // Dialog / Alert (6)
111        IconRole::DialogWarning => include_bytes!("../../icons/lucide/triangle-alert.svg"),
112        IconRole::DialogError => include_bytes!("../../icons/lucide/circle-x.svg"),
113        IconRole::DialogInfo => include_bytes!("../../icons/lucide/info.svg"),
114        IconRole::DialogQuestion => include_bytes!("../../icons/lucide/circle-question-mark.svg"),
115        IconRole::DialogSuccess => include_bytes!("../../icons/lucide/circle-check.svg"),
116        IconRole::Shield => include_bytes!("../../icons/lucide/shield.svg"),
117
118        // Window Controls (4)
119        IconRole::WindowClose => include_bytes!("../../icons/lucide/x.svg"),
120        IconRole::WindowMinimize => include_bytes!("../../icons/lucide/minimize.svg"),
121        IconRole::WindowMaximize => include_bytes!("../../icons/lucide/maximize.svg"),
122        IconRole::WindowRestore => include_bytes!("../../icons/lucide/minimize-2.svg"),
123
124        // Common Actions (14)
125        IconRole::ActionSave => include_bytes!("../../icons/lucide/save.svg"),
126        IconRole::ActionDelete => include_bytes!("../../icons/lucide/trash-2.svg"),
127        IconRole::ActionCopy => include_bytes!("../../icons/lucide/copy.svg"),
128        IconRole::ActionPaste => include_bytes!("../../icons/lucide/clipboard-paste.svg"),
129        IconRole::ActionCut => include_bytes!("../../icons/lucide/scissors.svg"),
130        IconRole::ActionUndo => include_bytes!("../../icons/lucide/undo-2.svg"),
131        IconRole::ActionRedo => include_bytes!("../../icons/lucide/redo-2.svg"),
132        IconRole::ActionSearch => include_bytes!("../../icons/lucide/search.svg"),
133        IconRole::ActionSettings => include_bytes!("../../icons/lucide/settings.svg"),
134        IconRole::ActionEdit => include_bytes!("../../icons/lucide/pencil.svg"),
135        IconRole::ActionAdd => include_bytes!("../../icons/lucide/plus.svg"),
136        IconRole::ActionRemove => include_bytes!("../../icons/lucide/minus.svg"),
137        IconRole::ActionRefresh => include_bytes!("../../icons/lucide/refresh-cw.svg"),
138        IconRole::ActionPrint => include_bytes!("../../icons/lucide/printer.svg"),
139
140        // Navigation (6)
141        IconRole::NavBack => include_bytes!("../../icons/lucide/chevron-left.svg"),
142        IconRole::NavForward => include_bytes!("../../icons/lucide/chevron-right.svg"),
143        IconRole::NavUp => include_bytes!("../../icons/lucide/chevron-up.svg"),
144        IconRole::NavDown => include_bytes!("../../icons/lucide/chevron-down.svg"),
145        IconRole::NavHome => include_bytes!("../../icons/lucide/house.svg"),
146        IconRole::NavMenu => include_bytes!("../../icons/lucide/menu.svg"),
147
148        // Files / Places (5)
149        IconRole::FileGeneric => include_bytes!("../../icons/lucide/file.svg"),
150        IconRole::FolderClosed => include_bytes!("../../icons/lucide/folder-closed.svg"),
151        IconRole::FolderOpen => include_bytes!("../../icons/lucide/folder-open.svg"),
152        IconRole::TrashEmpty => include_bytes!("../../icons/lucide/trash-2.svg"),
153        IconRole::TrashFull => include_bytes!("../../icons/lucide/trash-2.svg"), // reuse trash-2
154
155        // Status (3)
156        IconRole::StatusBusy => include_bytes!("../../icons/lucide/loader.svg"),
157        IconRole::StatusCheck => include_bytes!("../../icons/lucide/check.svg"),
158        IconRole::StatusError => include_bytes!("../../icons/lucide/circle-x.svg"), // reuse circle-x
159
160        // System (4)
161        IconRole::UserAccount => include_bytes!("../../icons/lucide/user.svg"),
162        IconRole::Notification => include_bytes!("../../icons/lucide/bell.svg"),
163        IconRole::Help => include_bytes!("../../icons/lucide/circle-question-mark.svg"), // reuse
164        IconRole::Lock => include_bytes!("../../icons/lucide/lock.svg"),
165
166        _ => return None, // #[non_exhaustive] forward compat
167    })
168}
169
170/// Returns raw SVG bytes for a bundled icon looked up by its canonical name
171/// within the icon set.
172///
173/// Names use each set's canonical format:
174/// - Lucide: kebab-case (e.g., `"arrow-down"`, `"circle-check"`)
175/// - Material: snake_case (e.g., `"arrow_downward"`, `"check_circle"`)
176///
177/// Returns `None` for non-bundled sets, disabled features, or unknown names.
178///
179/// # Examples
180///
181/// ```
182/// use native_theme::{IconSet, bundled_icon_by_name};
183///
184/// // Without features enabled, bundled icons return None
185/// let result = bundled_icon_by_name("check", IconSet::SfSymbols);
186/// assert!(result.is_none());
187/// ```
188#[must_use = "this returns SVG bytes; it does not render the icon"]
189#[allow(unreachable_patterns, unused_variables)]
190pub fn bundled_icon_by_name(name: &str, set: IconSet) -> Option<&'static [u8]> {
191    match set {
192        #[cfg(feature = "material-icons")]
193        IconSet::Material => material_svg_by_name(name),
194
195        #[cfg(feature = "lucide-icons")]
196        IconSet::Lucide => lucide_svg_by_name(name),
197
198        _ => None,
199    }
200}
201
202#[cfg(feature = "lucide-icons")]
203#[allow(unreachable_patterns)]
204fn lucide_svg_by_name(name: &str) -> Option<&'static [u8]> {
205    Some(match name {
206        "a-large-small" => include_bytes!("../../icons/lucide/a-large-small.svg"),
207        "arrow-down" => include_bytes!("../../icons/lucide/arrow-down.svg"),
208        "arrow-left" => include_bytes!("../../icons/lucide/arrow-left.svg"),
209        "arrow-right" => include_bytes!("../../icons/lucide/arrow-right.svg"),
210        "arrow-up" => include_bytes!("../../icons/lucide/arrow-up.svg"),
211        "asterisk" => include_bytes!("../../icons/lucide/asterisk.svg"),
212        "bell" => include_bytes!("../../icons/lucide/bell.svg"),
213        "book-open" => include_bytes!("../../icons/lucide/book-open.svg"),
214        "bot" => include_bytes!("../../icons/lucide/bot.svg"),
215        "building-2" => include_bytes!("../../icons/lucide/building-2.svg"),
216        "calendar" => include_bytes!("../../icons/lucide/calendar.svg"),
217        "case-sensitive" => include_bytes!("../../icons/lucide/case-sensitive.svg"),
218        "chart-pie" => include_bytes!("../../icons/lucide/chart-pie.svg"),
219        "check" => include_bytes!("../../icons/lucide/check.svg"),
220        "chevron-down" => include_bytes!("../../icons/lucide/chevron-down.svg"),
221        "chevron-left" => include_bytes!("../../icons/lucide/chevron-left.svg"),
222        "chevron-right" => include_bytes!("../../icons/lucide/chevron-right.svg"),
223        "chevrons-up-down" => include_bytes!("../../icons/lucide/chevrons-up-down.svg"),
224        "chevron-up" => include_bytes!("../../icons/lucide/chevron-up.svg"),
225        "circle-check" => include_bytes!("../../icons/lucide/circle-check.svg"),
226        "circle-question-mark" => include_bytes!("../../icons/lucide/circle-question-mark.svg"),
227        "circle-user" => include_bytes!("../../icons/lucide/circle-user.svg"),
228        "circle-x" => include_bytes!("../../icons/lucide/circle-x.svg"),
229        "clipboard-paste" => include_bytes!("../../icons/lucide/clipboard-paste.svg"),
230        "close" => include_bytes!("../../icons/lucide/close.svg"),
231        "copy" => include_bytes!("../../icons/lucide/copy.svg"),
232        "dash" => include_bytes!("../../icons/lucide/dash.svg"),
233        "delete" => include_bytes!("../../icons/lucide/delete.svg"),
234        "ellipsis" => include_bytes!("../../icons/lucide/ellipsis.svg"),
235        "ellipsis-vertical" => include_bytes!("../../icons/lucide/ellipsis-vertical.svg"),
236        "external-link" => include_bytes!("../../icons/lucide/external-link.svg"),
237        "eye" => include_bytes!("../../icons/lucide/eye.svg"),
238        "eye-off" => include_bytes!("../../icons/lucide/eye-off.svg"),
239        "file" => include_bytes!("../../icons/lucide/file.svg"),
240        "folder" => include_bytes!("../../icons/lucide/folder.svg"),
241        "folder-closed" => include_bytes!("../../icons/lucide/folder-closed.svg"),
242        "folder-open" => include_bytes!("../../icons/lucide/folder-open.svg"),
243        "frame" => include_bytes!("../../icons/lucide/frame.svg"),
244        "gallery-vertical-end" => include_bytes!("../../icons/lucide/gallery-vertical-end.svg"),
245        "github" => include_bytes!("../../icons/lucide/github.svg"),
246        "globe" => include_bytes!("../../icons/lucide/globe.svg"),
247        "heart" => include_bytes!("../../icons/lucide/heart.svg"),
248        "heart-off" => include_bytes!("../../icons/lucide/heart-off.svg"),
249        "house" => include_bytes!("../../icons/lucide/house.svg"),
250        "inbox" => include_bytes!("../../icons/lucide/inbox.svg"),
251        "info" => include_bytes!("../../icons/lucide/info.svg"),
252        "inspect" => include_bytes!("../../icons/lucide/inspect.svg"),
253        "layout-dashboard" => include_bytes!("../../icons/lucide/layout-dashboard.svg"),
254        "loader" => include_bytes!("../../icons/lucide/loader.svg"),
255        "loader-circle" => include_bytes!("../../icons/lucide/loader-circle.svg"),
256        "lock" => include_bytes!("../../icons/lucide/lock.svg"),
257        "map" => include_bytes!("../../icons/lucide/map.svg"),
258        "maximize" => include_bytes!("../../icons/lucide/maximize.svg"),
259        "menu" => include_bytes!("../../icons/lucide/menu.svg"),
260        "minimize" => include_bytes!("../../icons/lucide/minimize.svg"),
261        "minimize-2" => include_bytes!("../../icons/lucide/minimize-2.svg"),
262        "minus" => include_bytes!("../../icons/lucide/minus.svg"),
263        "moon" => include_bytes!("../../icons/lucide/moon.svg"),
264        "palette" => include_bytes!("../../icons/lucide/palette.svg"),
265        "panel-bottom" => include_bytes!("../../icons/lucide/panel-bottom.svg"),
266        "panel-bottom-open" => include_bytes!("../../icons/lucide/panel-bottom-open.svg"),
267        "panel-left" => include_bytes!("../../icons/lucide/panel-left.svg"),
268        "panel-left-close" => include_bytes!("../../icons/lucide/panel-left-close.svg"),
269        "panel-left-open" => include_bytes!("../../icons/lucide/panel-left-open.svg"),
270        "panel-right" => include_bytes!("../../icons/lucide/panel-right.svg"),
271        "panel-right-close" => include_bytes!("../../icons/lucide/panel-right-close.svg"),
272        "panel-right-open" => include_bytes!("../../icons/lucide/panel-right-open.svg"),
273        "pencil" => include_bytes!("../../icons/lucide/pencil.svg"),
274        "plus" => include_bytes!("../../icons/lucide/plus.svg"),
275        "printer" => include_bytes!("../../icons/lucide/printer.svg"),
276        "redo" => include_bytes!("../../icons/lucide/redo.svg"),
277        "redo-2" => include_bytes!("../../icons/lucide/redo-2.svg"),
278        "refresh-cw" => include_bytes!("../../icons/lucide/refresh-cw.svg"),
279        "replace" => include_bytes!("../../icons/lucide/replace.svg"),
280        "resize-corner" => include_bytes!("../../icons/lucide/resize-corner.svg"),
281        "save" => include_bytes!("../../icons/lucide/save.svg"),
282        "scissors" => include_bytes!("../../icons/lucide/scissors.svg"),
283        "search" => include_bytes!("../../icons/lucide/search.svg"),
284        "settings" => include_bytes!("../../icons/lucide/settings.svg"),
285        "settings-2" => include_bytes!("../../icons/lucide/settings-2.svg"),
286        "shield" => include_bytes!("../../icons/lucide/shield.svg"),
287        "sort-ascending" => include_bytes!("../../icons/lucide/sort-ascending.svg"),
288        "sort-descending" => include_bytes!("../../icons/lucide/sort-descending.svg"),
289        "square-terminal" => include_bytes!("../../icons/lucide/square-terminal.svg"),
290        "star" => include_bytes!("../../icons/lucide/star.svg"),
291        "star-off" => include_bytes!("../../icons/lucide/star-off.svg"),
292        "sun" => include_bytes!("../../icons/lucide/sun.svg"),
293        "thumbs-down" => include_bytes!("../../icons/lucide/thumbs-down.svg"),
294        "thumbs-up" => include_bytes!("../../icons/lucide/thumbs-up.svg"),
295        "trash-2" => include_bytes!("../../icons/lucide/trash-2.svg"),
296        "triangle-alert" => include_bytes!("../../icons/lucide/triangle-alert.svg"),
297        "undo" => include_bytes!("../../icons/lucide/undo.svg"),
298        "undo-2" => include_bytes!("../../icons/lucide/undo-2.svg"),
299        "user" => include_bytes!("../../icons/lucide/user.svg"),
300        "window-close" => include_bytes!("../../icons/lucide/window-close.svg"),
301        "window-maximize" => include_bytes!("../../icons/lucide/window-maximize.svg"),
302        "window-minimize" => include_bytes!("../../icons/lucide/window-minimize.svg"),
303        "window-restore" => include_bytes!("../../icons/lucide/window-restore.svg"),
304        "x" => include_bytes!("../../icons/lucide/x.svg"),
305        _ => return None,
306    })
307}
308
309#[cfg(feature = "material-icons")]
310#[allow(unreachable_patterns)]
311fn material_svg_by_name(name: &str) -> Option<&'static [u8]> {
312    Some(match name {
313        "account_circle" => include_bytes!("../../icons/material/account_circle.svg"),
314        "add" => include_bytes!("../../icons/material/add.svg"),
315        "apartment" => include_bytes!("../../icons/material/apartment.svg"),
316        "arrow_back" => include_bytes!("../../icons/material/arrow_back.svg"),
317        "arrow_downward" => include_bytes!("../../icons/material/arrow_downward.svg"),
318        "arrow_forward" => include_bytes!("../../icons/material/arrow_forward.svg"),
319        "arrow_upward" => include_bytes!("../../icons/material/arrow_upward.svg"),
320        "autorenew" => include_bytes!("../../icons/material/autorenew.svg"),
321        "calendar_today" => include_bytes!("../../icons/material/calendar_today.svg"),
322        "cancel" => include_bytes!("../../icons/material/cancel.svg"),
323        "check" => include_bytes!("../../icons/material/check.svg"),
324        "check_circle" => include_bytes!("../../icons/material/check_circle.svg"),
325        "chevron_left" => include_bytes!("../../icons/material/chevron_left.svg"),
326        "chevron_right" => include_bytes!("../../icons/material/chevron_right.svg"),
327        "close" => include_bytes!("../../icons/material/close.svg"),
328        "close_fullscreen" => include_bytes!("../../icons/material/close_fullscreen.svg"),
329        "code" => include_bytes!("../../icons/material/code.svg"),
330        "content_copy" => include_bytes!("../../icons/material/content_copy.svg"),
331        "crop_free" => include_bytes!("../../icons/material/crop_free.svg"),
332        "dark_mode" => include_bytes!("../../icons/material/dark_mode.svg"),
333        "dashboard" => include_bytes!("../../icons/material/dashboard.svg"),
334        "delete" => include_bytes!("../../icons/material/delete.svg"),
335        "description" => include_bytes!("../../icons/material/description.svg"),
336        "developer_mode" => include_bytes!("../../icons/material/developer_mode.svg"),
337        "dock_to_bottom" => include_bytes!("../../icons/material/dock_to_bottom.svg"),
338        "drag_indicator" => include_bytes!("../../icons/material/drag_indicator.svg"),
339        "emergency" => include_bytes!("../../icons/material/emergency.svg"),
340        "expand_less" => include_bytes!("../../icons/material/expand_less.svg"),
341        "expand_more" => include_bytes!("../../icons/material/expand_more.svg"),
342        "favorite" => include_bytes!("../../icons/material/favorite.svg"),
343        "find_replace" => include_bytes!("../../icons/material/find_replace.svg"),
344        "folder" => include_bytes!("../../icons/material/folder.svg"),
345        "folder_open" => include_bytes!("../../icons/material/folder_open.svg"),
346        "font_size" => include_bytes!("../../icons/material/font_size.svg"),
347        "heart_broken" => include_bytes!("../../icons/material/heart_broken.svg"),
348        "inbox" => include_bytes!("../../icons/material/inbox.svg"),
349        "info" => include_bytes!("../../icons/material/info.svg"),
350        "language" => include_bytes!("../../icons/material/language.svg"),
351        "left_panel_close" => include_bytes!("../../icons/material/left_panel_close.svg"),
352        "left_panel_open" => include_bytes!("../../icons/material/left_panel_open.svg"),
353        "light_mode" => include_bytes!("../../icons/material/light_mode.svg"),
354        "map" => include_bytes!("../../icons/material/map.svg"),
355        "match_case" => include_bytes!("../../icons/material/match_case.svg"),
356        "menu" => include_bytes!("../../icons/material/menu.svg"),
357        "menu_book" => include_bytes!("../../icons/material/menu_book.svg"),
358        "minimize" => include_bytes!("../../icons/material/minimize.svg"),
359        "more_horiz" => include_bytes!("../../icons/material/more_horiz.svg"),
360        "more_vert" => include_bytes!("../../icons/material/more_vert.svg"),
361        "notifications" => include_bytes!("../../icons/material/notifications.svg"),
362        "open_in_full" => include_bytes!("../../icons/material/open_in_full.svg"),
363        "open_in_new" => include_bytes!("../../icons/material/open_in_new.svg"),
364        "palette" => include_bytes!("../../icons/material/palette.svg"),
365        "person" => include_bytes!("../../icons/material/person.svg"),
366        "pie_chart" => include_bytes!("../../icons/material/pie_chart.svg"),
367        "progress_activity" => include_bytes!("../../icons/material/progress_activity.svg"),
368        "redo" => include_bytes!("../../icons/material/redo.svg"),
369        "remove" => include_bytes!("../../icons/material/remove.svg"),
370        "right_panel_close" => include_bytes!("../../icons/material/right_panel_close.svg"),
371        "right_panel_open" => include_bytes!("../../icons/material/right_panel_open.svg"),
372        "search" => include_bytes!("../../icons/material/search.svg"),
373        "settings" => include_bytes!("../../icons/material/settings.svg"),
374        "side_navigation" => include_bytes!("../../icons/material/side_navigation.svg"),
375        "smart_toy" => include_bytes!("../../icons/material/smart_toy.svg"),
376        "star" => include_bytes!("../../icons/material/star.svg"),
377        "star_border" => include_bytes!("../../icons/material/star_border.svg"),
378        "terminal" => include_bytes!("../../icons/material/terminal.svg"),
379        "thumb_down" => include_bytes!("../../icons/material/thumb_down.svg"),
380        "thumb_up" => include_bytes!("../../icons/material/thumb_up.svg"),
381        "tune" => include_bytes!("../../icons/material/tune.svg"),
382        "undo" => include_bytes!("../../icons/material/undo.svg"),
383        "unfold_more" => include_bytes!("../../icons/material/unfold_more.svg"),
384        "view_carousel" => include_bytes!("../../icons/material/view_carousel.svg"),
385        "visibility" => include_bytes!("../../icons/material/visibility.svg"),
386        "visibility_off" => include_bytes!("../../icons/material/visibility_off.svg"),
387        "warning" => include_bytes!("../../icons/material/warning.svg"),
388        "web_asset" => include_bytes!("../../icons/material/web_asset.svg"),
389        _ => return None,
390    })
391}
392
393#[cfg(test)]
394#[allow(clippy::unwrap_used, clippy::expect_used)]
395mod tests {
396    use super::*;
397
398    // === Feature-gated tests: Material ===
399
400    #[test]
401    #[cfg(feature = "material-icons")]
402    fn material_icons_cover_all_roles() {
403        for role in IconRole::ALL {
404            let svg = bundled_icon_svg(role, IconSet::Material);
405            assert!(svg.is_some(), "Material icons missing SVG for {:?}", role);
406            let bytes = svg.unwrap();
407            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
408            assert!(
409                content.contains("<svg"),
410                "Material {:?} does not contain <svg tag",
411                role
412            );
413        }
414    }
415
416    #[test]
417    #[cfg(feature = "material-icons")]
418    fn material_icons_total_size_under_200kb() {
419        let total: usize = IconRole::ALL
420            .iter()
421            .filter_map(|role| bundled_icon_svg(*role, IconSet::Material))
422            .map(|svg| svg.len())
423            .sum();
424        assert!(
425            total < 400 * 1024,
426            "Material icons total size {} bytes exceeds 400KB budget",
427            total
428        );
429    }
430
431    // === Feature-gated tests: Lucide ===
432
433    #[test]
434    #[cfg(feature = "lucide-icons")]
435    fn lucide_icons_cover_all_roles() {
436        for role in IconRole::ALL {
437            let svg = bundled_icon_svg(role, IconSet::Lucide);
438            assert!(svg.is_some(), "Lucide icons missing SVG for {:?}", role);
439            let bytes = svg.unwrap();
440            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
441            assert!(
442                content.contains("<svg"),
443                "Lucide {:?} does not contain <svg tag",
444                role
445            );
446        }
447    }
448
449    #[test]
450    #[cfg(feature = "lucide-icons")]
451    fn lucide_icons_total_size_under_100kb() {
452        let total: usize = IconRole::ALL
453            .iter()
454            .filter_map(|role| bundled_icon_svg(*role, IconSet::Lucide))
455            .map(|svg| svg.len())
456            .sum();
457        assert!(
458            total < 200 * 1024,
459            "Lucide icons total size {} bytes exceeds 200KB budget",
460            total
461        );
462    }
463
464    // === Non-feature-gated tests ===
465
466    #[test]
467    fn non_bundled_sets_return_none() {
468        assert!(
469            bundled_icon_svg(IconRole::ActionCopy, IconSet::SfSymbols).is_none(),
470            "SfSymbols should not be a bundled set"
471        );
472        assert!(
473            bundled_icon_svg(IconRole::ActionCopy, IconSet::Freedesktop).is_none(),
474            "Freedesktop should not be a bundled set"
475        );
476        assert!(
477            bundled_icon_svg(IconRole::ActionCopy, IconSet::SegoeIcons).is_none(),
478            "SegoeIcons should not be a bundled set"
479        );
480    }
481
482    // === bundled_icon_by_name tests ===
483
484    #[test]
485    #[cfg(feature = "lucide-icons")]
486    fn lucide_by_name_covers_gpui_icons() {
487        let names = [
488            "a-large-small",
489            "arrow-down",
490            "arrow-left",
491            "arrow-right",
492            "arrow-up",
493            "asterisk",
494            "bell",
495            "book-open",
496            "bot",
497            "building-2",
498            "calendar",
499            "case-sensitive",
500            "chart-pie",
501            "check",
502            "chevron-down",
503            "chevron-left",
504            "chevron-right",
505            "chevrons-up-down",
506            "chevron-up",
507            "circle-check",
508            "circle-user",
509            "circle-x",
510            "close",
511            "copy",
512            "dash",
513            "delete",
514            "ellipsis",
515            "ellipsis-vertical",
516            "external-link",
517            "eye",
518            "eye-off",
519            "file",
520            "folder",
521            "folder-closed",
522            "folder-open",
523            "frame",
524            "gallery-vertical-end",
525            "github",
526            "globe",
527            "heart",
528            "heart-off",
529            "inbox",
530            "info",
531            "inspect",
532            "layout-dashboard",
533            "loader",
534            "loader-circle",
535            "map",
536            "maximize",
537            "menu",
538            "minimize",
539            "minus",
540            "moon",
541            "palette",
542            "panel-bottom",
543            "panel-bottom-open",
544            "panel-left",
545            "panel-left-close",
546            "panel-left-open",
547            "panel-right",
548            "panel-right-close",
549            "panel-right-open",
550            "plus",
551            "redo",
552            "redo-2",
553            "replace",
554            "resize-corner",
555            "search",
556            "settings",
557            "settings-2",
558            "sort-ascending",
559            "sort-descending",
560            "square-terminal",
561            "star",
562            "star-off",
563            "sun",
564            "thumbs-down",
565            "thumbs-up",
566            "triangle-alert",
567            "undo",
568            "undo-2",
569            "user",
570            "window-close",
571            "window-maximize",
572            "window-minimize",
573            "window-restore",
574        ];
575        for name in names {
576            let svg = bundled_icon_by_name(name, IconSet::Lucide);
577            assert!(svg.is_some(), "Lucide by-name missing: {}", name);
578            let bytes = svg.unwrap();
579            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
580            assert!(
581                content.contains("<svg"),
582                "Lucide {} does not contain <svg tag",
583                name
584            );
585        }
586    }
587
588    #[test]
589    #[cfg(feature = "material-icons")]
590    fn material_by_name_covers_gpui_icons() {
591        let names = [
592            "font_size",
593            "arrow_downward",
594            "arrow_back",
595            "arrow_forward",
596            "arrow_upward",
597            "emergency",
598            "notifications",
599            "menu_book",
600            "smart_toy",
601            "apartment",
602            "calendar_today",
603            "match_case",
604            "pie_chart",
605            "check",
606            "expand_more",
607            "chevron_left",
608            "chevron_right",
609            "unfold_more",
610            "expand_less",
611            "check_circle",
612            "account_circle",
613            "cancel",
614            "close",
615            "content_copy",
616            "remove",
617            "delete",
618            "more_horiz",
619            "more_vert",
620            "open_in_new",
621            "visibility",
622            "visibility_off",
623            "description",
624            "folder",
625            "folder_open",
626            "crop_free",
627            "view_carousel",
628            "code",
629            "language",
630            "favorite",
631            "heart_broken",
632            "inbox",
633            "info",
634            "developer_mode",
635            "dashboard",
636            "progress_activity",
637            "autorenew",
638            "map",
639            "open_in_full",
640            "menu",
641            "minimize",
642            "dark_mode",
643            "palette",
644            "dock_to_bottom",
645            "web_asset",
646            "side_navigation",
647            "left_panel_close",
648            "left_panel_open",
649            "right_panel_close",
650            "right_panel_open",
651            "add",
652            "redo",
653            "find_replace",
654            "drag_indicator",
655            "search",
656            "settings",
657            "tune",
658            "terminal",
659            "star",
660            "star_border",
661            "light_mode",
662            "thumb_down",
663            "thumb_up",
664            "warning",
665            "undo",
666            "person",
667            "close_fullscreen",
668        ];
669        for name in names {
670            let svg = bundled_icon_by_name(name, IconSet::Material);
671            assert!(svg.is_some(), "Material by-name missing: {}", name);
672            let bytes = svg.unwrap();
673            let content = std::str::from_utf8(bytes).expect("SVG should be valid UTF-8");
674            assert!(
675                content.contains("<svg"),
676                "Material {} does not contain <svg tag",
677                name
678            );
679        }
680    }
681
682    #[test]
683    fn by_name_non_bundled_sets_return_none() {
684        assert!(bundled_icon_by_name("check", IconSet::SfSymbols).is_none());
685        assert!(bundled_icon_by_name("check", IconSet::Freedesktop).is_none());
686        assert!(bundled_icon_by_name("check", IconSet::SegoeIcons).is_none());
687    }
688
689    #[test]
690    fn by_name_unknown_name_returns_none() {
691        assert!(bundled_icon_by_name("nonexistent-icon-xyz", IconSet::Lucide).is_none());
692        assert!(bundled_icon_by_name("nonexistent_icon_xyz", IconSet::Material).is_none());
693    }
694}