Skip to main content

gpui_macos/
icon.rs

1//! Native SF Symbols on macOS, with the complete Lucide catalog as fallback.
2//!
3//! [`IconName`] and the SVG paths behind it come from `gpui-kit-assets`, so an
4//! application keeps the whole catalog and the same public names. On macOS,
5//! matching SF Symbols are supplied and rendered by the installed AppKit.
6//! Missing symbols and other platforms read SVGs from the application's `AssetSource`:
7//! register `gpui_kit::assets::Assets` for the default bundle, `AllAssets` for
8//! the full catalog, or compose either with `icon_assets!` for a few extras.
9use gpui_kit::base::StyledExt;
10use gpui_kit::rems;
11use gpui_kit::{App, IntoElement, RenderOnce, StyleRefinement, Styled, Window, svg};
12
13pub use gpui_kit::assets::IconName;
14
15#[derive(IntoElement)]
16pub struct Icon {
17    name: IconName,
18    style: StyleRefinement,
19}
20impl Styled for Icon {
21    fn style(&mut self) -> &mut StyleRefinement {
22        &mut self.style
23    }
24}
25impl RenderOnce for Icon {
26    fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
27        #[cfg(target_os = "macos")]
28        {
29            let text = window.text_style();
30            let color = self.style.text.color.unwrap_or(text.color);
31            let weight = self.style.text.font_weight.unwrap_or(text.font_weight);
32            let points = self
33                .style
34                .size
35                .height
36                .or(self.style.size.width)
37                .and_then(|length| match length {
38                    gpui_kit::Length::Definite(gpui_kit::DefiniteLength::Absolute(length)) => {
39                        Some(length.to_pixels(window.rem_size()).as_f32())
40                    }
41                    _ => None,
42                })
43                .unwrap_or_else(|| rems(1.).to_pixels(window.rem_size()).as_f32());
44            if let Some(image) = crate::native_symbols::image(
45                self.name,
46                points,
47                window.scale_factor(),
48                weight,
49                color,
50            ) {
51                return gpui_kit::img(image)
52                    .refine_style(&self.style)
53                    .into_any_element();
54            }
55        }
56        // Svg only paints when its own text color is set. Resolve inheritance here,
57        // then apply caller refinements so explicit size and color still win.
58        svg()
59            .path(self.name.path())
60            .text_color(window.text_style().color)
61            .refine_style(&self.style)
62            .into_any_element()
63    }
64}
65
66/// A 16px SF Symbol when supplied by macOS, otherwise a bundled SVG icon.
67pub fn icon(name: IconName) -> Icon {
68    Icon {
69        name,
70        style: StyleRefinement::default(),
71    }
72    .size(rems(1.))
73    .flex_shrink_0()
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use gpui_kit::AssetSource;
80
81    #[test]
82    fn the_icons_this_crate_draws_are_in_the_default_bundle() {
83        // Components reach for these without the application asking, so they
84        // must resolve against the bundle every gpui-kit application gets.
85        for name in [
86            IconName::Check,
87            IconName::Minus,
88            IconName::Plus,
89            IconName::ChevronDown,
90            IconName::ChevronRight,
91            IconName::ChevronLeft,
92            IconName::Calendar,
93            IconName::Star,
94            IconName::ExternalLink,
95            IconName::Close,
96            IconName::Search,
97            IconName::Menu,
98            IconName::Settings,
99            IconName::TriangleAlert,
100        ] {
101            let path = name.path();
102            assert!(
103                gpui_kit::assets::Assets.load(&path).unwrap().is_some(),
104                "{path}"
105            );
106        }
107    }
108}