ailloli_ui_core/ids/
icon_id.rs

1//! Cache-stable identifiers for built-in and custom icon sources.
2
3use std::hash::{Hash, Hasher};
4
5use lucide_icons::Icon;
6
7use super::svg_source::SvgSource;
8
9/// Icon source for rendering (`DrawCmd::Image` / `Icon` widget).
10///
11/// Values cover built-in chrome/actions, Lucide glyphs, Devicons codepoints,
12/// and custom SVG sources.
13///
14/// # Examples
15///
16/// ```
17/// use ailloli_ui_core::IconId;
18/// assert_eq!(IconId::Devicon('R'), IconId::Devicon('R'));
19/// assert_ne!(IconId::Close, IconId::Check);
20/// ```
21#[derive(Debug, Clone)]
22pub enum IconId {
23    /// Window minimize (chrome).
24    Minimize,
25    /// Window maximize (chrome).
26    Maximize,
27    /// Window close (chrome).
28    Close,
29    /// Copy action.
30    Copy,
31    /// Delete action.
32    Trash,
33    /// History action.
34    History,
35    /// Add action.
36    Plus,
37    /// Checkbox / check mark (Lucide `check`).
38    Check,
39    /// Arbitrary Lucide glyph.
40    Lucide(Icon),
41    /// Devicons Nerd Font codepoint.
42    Devicon(char),
43    /// Custom SVG rasterized at draw time.
44    Svg(SvgSource),
45}
46
47impl IconId {
48    /// Returns the stable tag mixed into manual equality and hashing.
49    fn discriminant_tag(&self) -> u8 {
50        match self {
51            IconId::Minimize => 0,
52            IconId::Maximize => 1,
53            IconId::Close => 2,
54            IconId::Copy => 3,
55            IconId::Trash => 4,
56            IconId::History => 5,
57            IconId::Plus => 6,
58            IconId::Check => 7,
59            IconId::Lucide(_) => 8,
60            IconId::Devicon(_) => 9,
61            IconId::Svg(_) => 10,
62        }
63    }
64}
65
66impl PartialEq for IconId {
67    /// Compares built-ins by variant and dynamic sources by their cache identity.
68    ///
69    /// Lucide comparison uses the icon's display name; SVG comparison follows
70    /// [`SvgSource`]'s pointer-identity contract rather than byte content.
71    fn eq(&self, other: &Self) -> bool {
72        if self.discriminant_tag() != other.discriminant_tag() {
73            return false;
74        }
75        match (self, other) {
76            (IconId::Minimize, IconId::Minimize)
77            | (IconId::Maximize, IconId::Maximize)
78            | (IconId::Close, IconId::Close)
79            | (IconId::Copy, IconId::Copy)
80            | (IconId::Trash, IconId::Trash)
81            | (IconId::History, IconId::History)
82            | (IconId::Plus, IconId::Plus)
83            | (IconId::Check, IconId::Check) => true,
84            (IconId::Lucide(a), IconId::Lucide(b)) => format!("{a}") == format!("{b}"),
85            (IconId::Devicon(a), IconId::Devicon(b)) => a == b,
86            (IconId::Svg(a), IconId::Svg(b)) => a == b,
87            _ => false,
88        }
89    }
90}
91
92impl Eq for IconId {}
93
94impl Hash for IconId {
95    /// Hashes the same discriminant and dynamic identity used by equality.
96    fn hash<H: Hasher>(&self, state: &mut H) {
97        self.discriminant_tag().hash(state);
98        match self {
99            IconId::Minimize
100            | IconId::Maximize
101            | IconId::Close
102            | IconId::Copy
103            | IconId::Trash
104            | IconId::History
105            | IconId::Plus
106            | IconId::Check => {}
107            IconId::Lucide(icon) => {
108                format!("{icon}").hash(state);
109            }
110            IconId::Devicon(ch) => ch.hash(state),
111            IconId::Svg(src) => src.hash(state),
112        }
113    }
114}