gpui-phosphor 0.1.0

An unofficial GPUI component and asset source for Phosphor Icons
Documentation
use crate::{IconError, IconName, Weight};
use gpui::{App, Hsla, IntoElement, Pixels, RenderOnce, SharedString, Styled, Window, px, svg};

/// A GPUI component that renders a typed Phosphor icon through GPUI’s `svg()`
/// element.
///
/// The component defaults to the first enabled weight, a 16px square, and the
/// current GPUI text color. Use [`Icon::try_new`] or [`Icon::try_with_weight`]
/// when a disabled weight should be reported before the component is rendered.
#[derive(IntoElement, Clone, Debug)]
pub struct Icon {
    name: IconName,
    weight: Weight,
    size: Pixels,
    color: Option<Hsla>,
}

impl Icon {
    /// Create an icon using the first enabled weight.
    #[must_use]
    pub fn new(name: IconName) -> Self {
        Self {
            name,
            weight: Weight::default_enabled(),
            size: px(16.0),
            color: None,
        }
    }

    /// Create an icon and validate that its weight is enabled.
    pub fn try_new(name: IconName, weight: Weight) -> Result<Self, IconError> {
        name.path(weight)?;
        Ok(Self {
            name,
            weight,
            size: px(16.0),
            color: None,
        })
    }

    /// Return the typed icon name.
    #[must_use]
    pub const fn name(&self) -> IconName {
        self.name
    }

    /// Return the selected visual weight.
    #[must_use]
    pub const fn icon_weight(&self) -> Weight {
        self.weight
    }

    /// Set the visual weight without validating its feature flag.
    ///
    /// Use [`Icon::try_with_weight`] when invalid feature configuration should
    /// be handled as a `Result`.
    #[must_use]
    pub fn weight(mut self, weight: Weight) -> Self {
        self.weight = weight;
        self
    }

    /// Set the visual weight and validate that it is enabled.
    pub fn try_with_weight(mut self, weight: Weight) -> Result<Self, IconError> {
        self.name.path(weight)?;
        self.weight = weight;
        Ok(self)
    }

    /// Set the rendered square size.
    #[must_use]
    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
        self.size = size.into();
        self
    }

    /// Set the rendered square size.
    #[must_use]
    pub fn with_size(self, size: impl Into<Pixels>) -> Self {
        self.size(size)
    }

    /// Return the configured square size.
    #[must_use]
    pub const fn icon_size(&self) -> Pixels {
        self.size
    }

    /// Set the SVG’s color. If omitted, GPUI’s current text color is used.
    #[must_use]
    pub fn color(mut self, color: impl Into<Hsla>) -> Self {
        self.color = Some(color.into());
        self
    }

    /// Set the SVG’s color. This is an alias for [`Icon::color`].
    #[must_use]
    pub fn text_color(self, color: impl Into<Hsla>) -> Self {
        self.color(color)
    }

    /// Return the explicitly configured color, if there is one.
    #[must_use]
    pub const fn icon_color(&self) -> Option<Hsla> {
        self.color
    }

    /// Resolve the selected icon to its namespaced GPUI asset path.
    pub fn path(&self) -> Result<SharedString, IconError> {
        self.name.path(self.weight)
    }
}

impl RenderOnce for Icon {
    fn render(self, window: &mut Window, _cx: &mut App) -> impl IntoElement {
        let path = self.path().unwrap_or_else(|_| {
            // Keep the render path deterministic so PhosphorAssets can report
            // the disabled feature through GPUI’s asset-loading error path.
            format!(
                "phosphor/{}/{}.svg",
                self.weight.as_str(),
                self.name.as_str()
            )
            .into()
        });
        svg()
            .flex_none()
            .size(self.size)
            .text_color(self.color.unwrap_or(window.text_style().color))
            .path(path)
    }
}