1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum IconVariant {
13 Outline,
15 Filled,
17 Rounded,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub enum IconName {
24 Close,
26 Menu,
28 ArrowRight,
30 ArrowLeft,
32 ArrowUp,
34 ArrowDown,
36 Check,
38 Search,
40}
41
42pub trait IconSet: Send + Sync {
48 fn path_data(&self, icon: IconName, variant: IconVariant, size: u32) -> Option<&'static str>;
53}
54
55pub struct BuiltinIcons;
61
62impl BuiltinIcons {
63 pub fn new() -> Self {
65 Self
66 }
67}
68
69impl Default for BuiltinIcons {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl IconSet for BuiltinIcons {
76 fn path_data(&self, icon: IconName, variant: IconVariant, size: u32) -> Option<&'static str> {
77 match (icon, variant, size) {
78 (IconName::Close, IconVariant::Outline | IconVariant::Rounded, 16) => {
80 Some("M3 3L13 13M13 3L3 13")
81 }
82 (IconName::Close, IconVariant::Outline | IconVariant::Rounded, 20) => {
83 Some("M4 4L16 16M16 4L4 16")
84 }
85 (IconName::Close, IconVariant::Outline | IconVariant::Rounded, 24) => {
86 Some("M5 5L19 19M19 5L5 19")
87 }
88 (IconName::Close, IconVariant::Outline | IconVariant::Rounded, 32) => {
89 Some("M6 6L26 26M26 6L6 26")
90 }
91 (IconName::Close, IconVariant::Filled, 16) => Some("M3 3L13 13M13 3L3 13"),
92 (IconName::Close, IconVariant::Filled, 20) => Some("M4 4L16 16M16 4L4 16"),
93 (IconName::Close, IconVariant::Filled, 24) => Some("M5 5L19 19M19 5L5 19"),
94 (IconName::Close, IconVariant::Filled, 32) => Some("M6 6L26 26M26 6L6 26"),
95
96 (IconName::Menu, _, 16) => Some("M2 5H14M2 8H14M2 11H14"),
98 (IconName::Menu, _, 20) => Some("M3 6H17M3 10H17M3 14H17"),
99 (IconName::Menu, _, 24) => Some("M4 7H20M4 12H20M4 17H20"),
100 (IconName::Menu, _, 32) => Some("M5 9H27M5 16H27M5 23H27"),
101
102 (IconName::ArrowRight, IconVariant::Outline | IconVariant::Rounded, 16) => {
104 Some("M4 8H12M9 5L12 8L9 11")
105 }
106 (IconName::ArrowRight, _, 20) => Some("M5 10H15M11 6L15 10L11 14"),
107 (IconName::ArrowRight, _, 24) => Some("M6 12H18M13 7L18 12L13 17"),
108 (IconName::ArrowRight, _, 32) => Some("M8 16H24M17 9L24 16L17 23"),
109 (IconName::ArrowRight, IconVariant::Filled, 16) => Some("M4 8H12M9 5L12 8L9 11"),
110
111 (IconName::ArrowLeft, IconVariant::Outline | IconVariant::Rounded, 16) => {
113 Some("M12 8H4M7 5L4 8L7 11")
114 }
115 (IconName::ArrowLeft, _, 20) => Some("M15 10H5M9 6L5 10L9 14"),
116 (IconName::ArrowLeft, _, 24) => Some("M18 12H6M11 7L6 12L11 17"),
117 (IconName::ArrowLeft, _, 32) => Some("M24 16H8M15 9L8 16L15 23"),
118 (IconName::ArrowLeft, IconVariant::Filled, 16) => Some("M12 8H4M7 5L4 8L7 11"),
119
120 (IconName::ArrowUp, _, 16) => Some("M8 12V4M5 7L8 4L11 7"),
122 (IconName::ArrowUp, _, 20) => Some("M10 15V5M6 9L10 5L14 9"),
123 (IconName::ArrowUp, _, 24) => Some("M12 19V5M7 10L12 5L17 10"),
124 (IconName::ArrowUp, _, 32) => Some("M16 26V6M9 13L16 6L23 13"),
125
126 (IconName::ArrowDown, _, 16) => Some("M8 4V12M5 9L8 12L11 9"),
128 (IconName::ArrowDown, _, 20) => Some("M10 5V15M6 11L10 15L14 11"),
129 (IconName::ArrowDown, _, 24) => Some("M12 5V19M7 14L12 19L17 14"),
130 (IconName::ArrowDown, _, 32) => Some("M16 6V26M9 19L16 26L23 19"),
131
132 (IconName::Check, _, 16) => Some("M2 8L6 12L14 4"),
134 (IconName::Check, _, 20) => Some("M3 10L8 15L17 5"),
135 (IconName::Check, _, 24) => Some("M4 12L9 18L20 6"),
136 (IconName::Check, _, 32) => Some("M5 16L12 24L27 8"),
137
138 (IconName::Search, _, 16) => Some("M7 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0-8 0M11 11L14 14"),
140 (IconName::Search, _, 20) => {
141 Some("M9 9m-5 0a5 5 0 1 0 10 0a5 5 0 1 0-10 0M14 14L18 18")
142 }
143 (IconName::Search, _, 24) => {
144 Some("M11 11m-6 0a6 6 0 1 0 12 0a6 6 0 1 0-12 0M17 17L21 21")
145 }
146 (IconName::Search, _, 32) => {
147 Some("M14 14m-8 0a8 8 0 1 0 16 0a8 8 0 1 0-16 0M22 22L28 28")
148 }
149
150 _ => None,
151 }
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn close_all_sizes_outline() {
161 let icons = BuiltinIcons::new();
162 for size in [16u32, 20, 24, 32] {
163 assert!(
164 icons
165 .path_data(IconName::Close, IconVariant::Outline, size)
166 .is_some(),
167 "Close outline missing for size {size}"
168 );
169 }
170 }
171
172 #[test]
173 fn close_all_sizes_filled() {
174 let icons = BuiltinIcons::new();
175 for size in [16u32, 20, 24, 32] {
176 assert!(
177 icons
178 .path_data(IconName::Close, IconVariant::Filled, size)
179 .is_some(),
180 "Close filled missing for size {size}"
181 );
182 }
183 }
184
185 #[test]
186 fn all_icons_have_24px_outline() {
187 let icons = BuiltinIcons::new();
188 let all = [
189 IconName::Close,
190 IconName::Menu,
191 IconName::ArrowRight,
192 IconName::ArrowLeft,
193 IconName::ArrowUp,
194 IconName::ArrowDown,
195 IconName::Check,
196 IconName::Search,
197 ];
198 for icon in all {
199 assert!(
200 icons.path_data(icon, IconVariant::Outline, 24).is_some(),
201 "{icon:?} missing 24px outline"
202 );
203 }
204 }
205
206 #[test]
207 fn rounded_aliases_outline_for_close() {
208 let icons = BuiltinIcons::new();
209 let outline = icons.path_data(IconName::Close, IconVariant::Outline, 24);
210 let rounded = icons.path_data(IconName::Close, IconVariant::Rounded, 24);
211 assert_eq!(outline, rounded);
212 }
213
214 #[test]
215 fn unknown_size_returns_none() {
216 let icons = BuiltinIcons::new();
217 assert!(icons
218 .path_data(IconName::Close, IconVariant::Outline, 48)
219 .is_none());
220 }
221}