Skip to main content

gpui_cn/
icon.rs

1use gpui_kit::{
2    App, IntoElement, Radians, RenderOnce, SharedString, StyleRefinement, Styled, Svg,
3    Transformation, Window, base::StyledExt as _, prelude::FluentBuilder as _, svg,
4};
5
6/// Where an icon's SVG comes from.
7#[derive(Clone, Debug)]
8pub enum IconSource {
9    /// A path resolved by the application's `AssetSource`, such as
10    /// `icons/check.svg`.
11    Path(SharedString),
12    /// SVG bytes embedded in the binary, with no asset lookup.
13    Bytes(&'static [u8]),
14}
15
16/// An SVG icon sized on the rem scale and painted with the text color.
17///
18/// The bundled Lucide `IconName` already renders on its own at the font
19/// size. This type is for icons an application brings itself (a path
20/// or embedded bytes) and for sizing independent of the font, which a
21/// button needs. Defaults to `size_4` (16px at the default font size). Any
22/// `Styled` method refines it:
23///
24/// ```
25/// use gpui_cn::Icon;
26/// use gpui_kit::Styled as _;
27///
28/// const DOT: &[u8] = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/></svg>"#;
29///
30/// let _ = Icon::new("icons/check.svg").size_5();
31/// let _ = Icon::from_bytes(DOT);
32/// let _ = Icon::from(DOT);
33/// ```
34///
35/// An `IconName` from the Lucide catalog in `gpui_kit::assets` converts
36/// into an icon: `Icon::from(IconName::Check)`. The application registers
37/// the asset source that holds the SVG files, `gpui_kit::assets::Assets`.
38#[derive(Clone, IntoElement)]
39pub struct Icon {
40    source: IconSource,
41    style: StyleRefinement,
42    rotation: Option<Radians>,
43}
44
45impl Icon {
46    /// An icon from an asset path.
47    pub fn new(path: impl Into<SharedString>) -> Self {
48        Self::from_source(IconSource::Path(path.into()))
49    }
50
51    /// An icon from embedded SVG bytes.
52    pub fn from_bytes(bytes: &'static [u8]) -> Self {
53        Self::from_source(IconSource::Bytes(bytes))
54    }
55
56    fn from_source(source: IconSource) -> Self {
57        Self {
58            source,
59            style: StyleRefinement::default(),
60            rotation: None,
61        }
62    }
63
64    /// Turns the icon about its center, clockwise. A chevron that turns
65    /// with a fold uses it.
66    pub fn rotate(mut self, angle: impl Into<Radians>) -> Self {
67        self.rotation = Some(angle.into());
68        self
69    }
70
71    /// Where the SVG comes from.
72    pub fn source(&self) -> &IconSource {
73        &self.source
74    }
75}
76
77impl From<&'static str> for Icon {
78    fn from(path: &'static str) -> Self {
79        Self::new(path)
80    }
81}
82
83impl From<SharedString> for Icon {
84    fn from(path: SharedString) -> Self {
85        Self::new(path)
86    }
87}
88
89impl From<&'static [u8]> for Icon {
90    fn from(bytes: &'static [u8]) -> Self {
91        Self::from_bytes(bytes)
92    }
93}
94
95/// `include_bytes!` yields an array reference, which does not coerce
96/// through `impl Into<Icon>`, so arrays convert directly.
97impl<const N: usize> From<&'static [u8; N]> for Icon {
98    fn from(bytes: &'static [u8; N]) -> Self {
99        Self::from_bytes(bytes)
100    }
101}
102
103impl From<gpui_kit::assets::IconName> for Icon {
104    fn from(name: gpui_kit::assets::IconName) -> Self {
105        Self::new(name.path())
106    }
107}
108
109impl Styled for Icon {
110    fn style(&mut self) -> &mut StyleRefinement {
111        &mut self.style
112    }
113}
114
115impl RenderOnce for Icon {
116    fn render(self, window: &mut Window, _: &mut App) -> impl IntoElement {
117        let text_style = window.text_style();
118        let element: Svg = match &self.source {
119            IconSource::Path(path) => svg().path(path.clone()),
120            IconSource::Bytes(bytes) => svg().data(bytes),
121        };
122        element
123            .flex_shrink_0()
124            .size_4()
125            .text_color(text_style.color)
126            .when_some(self.rotation, |this, angle| {
127                this.with_transformation(Transformation::rotate(angle))
128            })
129            .refine_style(&self.style)
130    }
131}