1mod lucide;
12
13pub use lucide::{IconName, LUCIDE_VERSION};
14
15use std::borrow::Cow;
16use std::sync::atomic::{AtomicBool, Ordering};
17
18use gpui::prelude::*;
19use gpui::{div, px, App, IntoElement, SharedString, Window};
20
21use crate::devtools::Probed;
22use crate::theme::{theme, ColorName, Size};
23
24pub(crate) const FONT_FAMILY: &str = "lucide";
26
27static FONT_BYTES: &[u8] = include_bytes!("../../assets/lucide/lucide.ttf");
28static FONT_REGISTERED: AtomicBool = AtomicBool::new(false);
29
30pub(crate) fn ensure_font(cx: &App) {
33 if !FONT_REGISTERED.swap(true, Ordering::Relaxed) {
34 cx.text_system()
35 .add_fonts(vec![Cow::Borrowed(FONT_BYTES)])
36 .expect("failed to register the embedded Lucide icon font");
37 }
38}
39
40#[derive(Debug, Clone, IntoElement)]
44pub enum Glyph {
45 Lucide(IconName),
46 Text(SharedString),
47}
48
49impl From<IconName> for Glyph {
50 fn from(name: IconName) -> Self {
51 Glyph::Lucide(name)
52 }
53}
54
55impl From<&'static str> for Glyph {
56 fn from(text: &'static str) -> Self {
57 Glyph::Text(SharedString::new_static(text))
58 }
59}
60
61impl From<String> for Glyph {
62 fn from(text: String) -> Self {
63 Glyph::Text(text.into())
64 }
65}
66
67impl From<SharedString> for Glyph {
68 fn from(text: SharedString) -> Self {
69 Glyph::Text(text)
70 }
71}
72
73impl RenderOnce for Glyph {
74 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
75 let element = match self {
76 Glyph::Lucide(name) => {
77 ensure_font(cx);
78 div()
79 .font_family(FONT_FAMILY)
80 .child(SharedString::new_static(name.glyph()))
81 }
82 Glyph::Text(text) => div().child(text),
83 };
84
85 element.probe("Glyph")
86 }
87}
88
89#[derive(IntoElement)]
94pub struct Icon {
95 name: IconName,
96 size: Size,
97 color: Option<ColorName>,
98}
99
100impl Icon {
101 pub fn new(name: IconName) -> Self {
102 Icon {
103 name,
104 size: Size::Md,
105 color: None,
106 }
107 }
108
109 pub fn size(mut self, size: Size) -> Self {
110 self.size = size;
111 self
112 }
113
114 pub fn color(mut self, color: ColorName) -> Self {
116 self.color = Some(color);
117 self
118 }
119
120 fn glyph_px(&self) -> f32 {
121 match self.size {
122 Size::Xs => 14.0,
123 Size::Sm => 16.0,
124 Size::Md => 20.0,
125 Size::Lg => 26.0,
126 Size::Xl => 32.0,
127 }
128 }
129}
130
131impl RenderOnce for Icon {
132 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
133 ensure_font(cx);
134 let t = theme(cx);
135 let tint = self.color.map(|c| t.color(c, t.primary_shade()).hsla());
136 let size = px(self.glyph_px());
137 let mut el = div()
138 .flex()
139 .items_center()
140 .justify_center()
141 .font_family(FONT_FAMILY)
142 .text_size(size)
143 .line_height(size)
144 .child(SharedString::new_static(self.name.glyph()));
145 if let Some(tint) = tint {
146 el = el.text_color(tint);
147 }
148 el.probe("Icon")
149 .attr("name", self.name.name())
150 .attr("size", self.size.label())
151 }
152}