Skip to main content

khora_core/ui/editor/
icons.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Editor icon set — names and Unicode codepoints from the **Lucide** font.
16//!
17//! Each variant maps to a Private-Use-Area codepoint defined by `lucide.ttf`
18//! (v0.469.0). Apps render an icon by passing
19//! [`Icon::glyph()`] to [`UiBuilder::paint_text_styled`] with
20//! [`FontFamilyHint::Icons`], or by using a higher-level widget that does
21//! that internally.
22//!
23//! See <https://lucide.dev/icons/> for visual previews.
24
25/// A semantic icon identifier mapped to a Lucide codepoint.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27#[allow(missing_docs)]
28pub enum Icon {
29    // Navigation
30    Search,
31    ChevronDown,
32    ChevronRight,
33    ArrowRight,
34
35    // 3D / scene
36    Cube,
37    Light,
38    Camera,
39    Folder,
40    Layers,
41    Globe,
42    Axes,
43    Grid,
44    Sparkles,
45    Package,
46
47    // Transport / play
48    Play,
49    Pause,
50    Stop,
51    StepForward,
52
53    // Visibility / lock
54    Eye,
55    EyeOff,
56    Lock,
57
58    // Common UI
59    Plus,
60    More,
61    Filter,
62    Trash,
63    Bell,
64    Settings,
65    Share,
66    Hammer,
67    Branch,
68    Menu,
69    Tag,
70
71    // Tools
72    Move,
73    Rotate,
74    Scale,
75    Hand,
76    Crosshair,
77
78    // Bottom panels
79    Database,
80    Film,
81    Terminal,
82    Code,
83    Image,
84    Music,
85    Pen,
86
87    // Status / log levels
88    Info,
89    Warn,
90    Error,
91
92    // Brand / accents
93    Zap,
94    Command,
95
96    // Hardware
97    Wifi,
98    Cpu,
99    Memory,
100
101    // Confirmation / dismissal
102    Check,
103    Close,
104
105    // Files / source control
106    FolderOpen,
107    Save,
108    Copy,
109    Pencil,
110    Download,
111    Refresh,
112    Github,
113    ArrowLeft,
114
115    // Misc
116    Box,
117    Dot,
118    Circle,
119    CheckCircle,
120}
121
122impl Icon {
123    /// Returns the Lucide PUA glyph for this icon as a `&'static str`.
124    /// Use it with `paint_text_styled(.., FontFamilyHint::Icons, ..)` or with
125    /// any widget that renders icon text.
126    pub fn glyph(self) -> &'static str {
127        match self {
128            // Navigation
129            Self::Search => "\u{e154}",
130            Self::ChevronDown => "\u{e071}",
131            Self::ChevronRight => "\u{e073}",
132            Self::ArrowRight => "\u{e04d}",
133
134            // 3D / scene
135            Self::Cube => "\u{e065}",  // box
136            Self::Light => "\u{e1c1}", // lightbulb
137            Self::Camera => "\u{e068}",
138            Self::Folder => "\u{e0dc}",
139            Self::Layers => "\u{e52d}",
140            Self::Globe => "\u{e0eb}",
141            Self::Axes => "\u{e2fd}",
142            Self::Grid => "\u{e0ec}",
143            Self::Sparkles => "\u{e416}",
144            Self::Package => "\u{e12c}",
145
146            // Transport
147            Self::Play => "\u{e13f}",
148            Self::Pause => "\u{e131}",
149            Self::Stop => "\u{e16a}", // square
150            Self::StepForward => "\u{e3ed}",
151
152            // Visibility / lock
153            Self::Eye => "\u{e0be}",
154            Self::EyeOff => "\u{e0bf}",
155            Self::Lock => "\u{e10e}",
156
157            // Common UI
158            Self::Plus => "\u{e140}",
159            Self::More => "\u{e118}", // menu
160            Self::Filter => "\u{e0d5}",
161            Self::Trash => "\u{e18d}",
162            Self::Bell => "\u{e05d}",
163            Self::Settings => "\u{e157}",
164            Self::Share => "\u{e159}",
165            Self::Hammer => "\u{e0ef}",
166            Self::Branch => "\u{e0e5}",
167            Self::Menu => "\u{e118}",
168            Self::Tag => "\u{e182}",
169
170            // Tools
171            Self::Move => "\u{e124}",
172            Self::Rotate => "\u{e2e9}",
173            Self::Scale => "\u{e211}",
174            Self::Hand => "\u{e1d6}",
175            Self::Crosshair => "\u{e0b0}",
176
177            // Bottom panels
178            Self::Database => "\u{e0b1}",
179            Self::Film => "\u{e0d4}",
180            Self::Terminal => "\u{e184}",
181            Self::Code => "\u{e097}",
182            Self::Image => "\u{e0f9}",
183            Self::Music => "\u{e34d}",
184            Self::Pen => "\u{e132}",
185
186            // Status / log levels
187            Self::Info => "\u{e0fe}",
188            Self::Warn => "\u{e192}",  // triangle-alert
189            Self::Error => "\u{e088}", // circle-x
190
191            // Brand / accents
192            Self::Zap => "\u{e1b3}",
193            Self::Command => "\u{e09e}",
194
195            // Hardware
196            Self::Wifi => "\u{e1ad}",
197            Self::Cpu => "\u{e0ad}",
198            Self::Memory => "\u{e449}",
199
200            // Confirmation / dismissal
201            Self::Check => "\u{e070}",
202            Self::Close => "\u{e1b1}", // x
203
204            // Files / source control
205            Self::FolderOpen => "\u{e246}",
206            Self::Save => "\u{e150}",
207            Self::Copy => "\u{e0a2}",
208            Self::Pencil => "\u{e1f8}",
209            Self::Download => "\u{e0b6}",
210            Self::Refresh => "\u{e148}", // refresh-cw
211            Self::Github => "\u{e0e9}",
212            Self::ArrowLeft => "\u{e04c}",
213
214            // Misc
215            Self::Box => "\u{e065}",
216            Self::Dot => "\u{e453}",
217            Self::Circle => "\u{e07a}",
218            Self::CheckCircle => "\u{e225}",
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::Icon;
226
227    /// Every glyph is a single character in the Private Use Area — an icon
228    /// font maps one codepoint per icon, so anything else means a typo'd
229    /// escape that would silently render as tofu.
230    #[test]
231    fn every_glyph_is_one_pua_codepoint() {
232        const ALL: &[Icon] = &[
233            Icon::Search,
234            Icon::ChevronDown,
235            Icon::ChevronRight,
236            Icon::ArrowRight,
237            Icon::Cube,
238            Icon::Light,
239            Icon::Camera,
240            Icon::Folder,
241            Icon::Layers,
242            Icon::Globe,
243            Icon::Axes,
244            Icon::Grid,
245            Icon::Sparkles,
246            Icon::Package,
247            Icon::Play,
248            Icon::Pause,
249            Icon::Stop,
250            Icon::StepForward,
251            Icon::Eye,
252            Icon::EyeOff,
253            Icon::Lock,
254            Icon::Plus,
255            Icon::More,
256            Icon::Filter,
257            Icon::Trash,
258            Icon::Bell,
259            Icon::Settings,
260            Icon::Share,
261            Icon::Hammer,
262            Icon::Branch,
263            Icon::Menu,
264            Icon::Tag,
265            Icon::Move,
266            Icon::Rotate,
267            Icon::Scale,
268            Icon::Hand,
269            Icon::Crosshair,
270            Icon::Database,
271            Icon::Film,
272            Icon::Terminal,
273            Icon::Code,
274            Icon::Image,
275            Icon::Music,
276            Icon::Pen,
277            Icon::Info,
278            Icon::Warn,
279            Icon::Error,
280            Icon::Zap,
281            Icon::Command,
282            Icon::Wifi,
283            Icon::Cpu,
284            Icon::Memory,
285            Icon::Check,
286            Icon::Close,
287            Icon::FolderOpen,
288            Icon::Save,
289            Icon::Copy,
290            Icon::Pencil,
291            Icon::Download,
292            Icon::Refresh,
293            Icon::Github,
294            Icon::ArrowLeft,
295            Icon::Box,
296            Icon::Dot,
297            Icon::Circle,
298            Icon::CheckCircle,
299        ];
300
301        for icon in ALL {
302            let g = icon.glyph();
303            let mut chars = g.chars();
304            let c = chars.next().expect("glyph must not be empty");
305            assert!(
306                chars.next().is_none(),
307                "{icon:?} maps to more than one char"
308            );
309            assert!(
310                ('\u{e000}'..='\u{f8ff}').contains(&c),
311                "{icon:?} is outside the Private Use Area — the icon font won't have it"
312            );
313        }
314    }
315}