Skip to main content

gpui_phosphor/
icon.rs

1use crate::{IconError, IconName, Weight};
2use gpui::{App, Hsla, IntoElement, Pixels, RenderOnce, SharedString, Styled, Window, px, svg};
3
4/// A GPUI component that renders a typed Phosphor icon through GPUI’s `svg()`
5/// element.
6///
7/// The component defaults to the first enabled weight, a 16px square, and the
8/// current GPUI text color. Use [`Icon::try_new`] or [`Icon::try_with_weight`]
9/// when a disabled weight should be reported before the component is rendered.
10#[derive(IntoElement, Clone, Debug)]
11pub struct Icon {
12    name: IconName,
13    weight: Weight,
14    size: Pixels,
15    color: Option<Hsla>,
16}
17
18impl Icon {
19    /// Create an icon using the first enabled weight.
20    #[must_use]
21    pub fn new(name: IconName) -> Self {
22        Self {
23            name,
24            weight: Weight::default_enabled(),
25            size: px(16.0),
26            color: None,
27        }
28    }
29
30    /// Create an icon and validate that its weight is enabled.
31    pub fn try_new(name: IconName, weight: Weight) -> Result<Self, IconError> {
32        name.path(weight)?;
33        Ok(Self {
34            name,
35            weight,
36            size: px(16.0),
37            color: None,
38        })
39    }
40
41    /// Return the typed icon name.
42    #[must_use]
43    pub const fn name(&self) -> IconName {
44        self.name
45    }
46
47    /// Return the selected visual weight.
48    #[must_use]
49    pub const fn icon_weight(&self) -> Weight {
50        self.weight
51    }
52
53    /// Set the visual weight without validating its feature flag.
54    ///
55    /// Use [`Icon::try_with_weight`] when invalid feature configuration should
56    /// be handled as a `Result`.
57    #[must_use]
58    pub fn weight(mut self, weight: Weight) -> Self {
59        self.weight = weight;
60        self
61    }
62
63    /// Set the visual weight and validate that it is enabled.
64    pub fn try_with_weight(mut self, weight: Weight) -> Result<Self, IconError> {
65        self.name.path(weight)?;
66        self.weight = weight;
67        Ok(self)
68    }
69
70    /// Set the rendered square size.
71    #[must_use]
72    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
73        self.size = size.into();
74        self
75    }
76
77    /// Set the rendered square size.
78    #[must_use]
79    pub fn with_size(self, size: impl Into<Pixels>) -> Self {
80        self.size(size)
81    }
82
83    /// Return the configured square size.
84    #[must_use]
85    pub const fn icon_size(&self) -> Pixels {
86        self.size
87    }
88
89    /// Set the SVG’s color. If omitted, GPUI’s current text color is used.
90    #[must_use]
91    pub fn color(mut self, color: impl Into<Hsla>) -> Self {
92        self.color = Some(color.into());
93        self
94    }
95
96    /// Set the SVG’s color. This is an alias for [`Icon::color`].
97    #[must_use]
98    pub fn text_color(self, color: impl Into<Hsla>) -> Self {
99        self.color(color)
100    }
101
102    /// Return the explicitly configured color, if there is one.
103    #[must_use]
104    pub const fn icon_color(&self) -> Option<Hsla> {
105        self.color
106    }
107
108    /// Resolve the selected icon to its namespaced GPUI asset path.
109    pub fn path(&self) -> Result<SharedString, IconError> {
110        self.name.path(self.weight)
111    }
112}
113
114impl RenderOnce for Icon {
115    fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
116        let path = self.path().unwrap_or_else(|_| {
117            // Keep the render path deterministic so PhosphorAssets can report
118            // the disabled feature through GPUI’s asset-loading error path.
119            format!(
120                "phosphor/{}/{}.svg",
121                self.weight.as_str(),
122                self.name.as_str()
123            )
124            .into()
125        });
126        svg()
127            .flex_none()
128            .size(self.size)
129            .text_color(self.color.unwrap_or(window.text_style().color))
130            .path(path)
131    }
132}