fluidattacks-designs 0.11.0

Fluid Attacks design system, GPUI variant: desktop widgets over the shared tokens
//! `Icon` — the web `FontAwesome` icon: a glyph name, a style, a size, and a
//! color that falls back to the theme's text color, the way the web's
//! `currentColor` resolves.
//!
//! Glyphs resolve at runtime through [`crate::Assets`]; without the icons
//! directory nothing paints, mirroring how fonts degrade.

use fluidattacks_designs_domain::icon::{Size, Style};
use gpui::{
    img, px, rgb, svg, App, IntoElement, RenderOnce, Rgba, SharedString, Styled as _, Window,
};

#[derive(IntoElement)]
pub struct Icon {
    name: SharedString,
    style: Style,
    size: Size,
    side: Option<f32>,
    color: Option<Rgba>,
}

impl Icon {
    pub fn new(name: impl Into<SharedString>) -> Self {
        Self {
            name: name.into(),
            style: Style::default(),
            size: Size::default(),
            side: None,
            color: None,
        }
    }

    #[must_use]
    pub const fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    #[must_use]
    pub const fn size(mut self, size: Size) -> Self {
        self.size = size;
        self
    }

    /// A side off the icon scale, which the web takes as a custom px size.
    /// Crate-private: published it would put an unchecked `f32` on the
    /// rasterizer, and only a component overriding its own glyph needs it —
    /// the checkbox paints its mark at 8px inside a 16px box.
    #[must_use]
    pub(crate) const fn size_px(mut self, side: f32) -> Self {
        self.side = Some(side);
        self
    }

    #[must_use]
    pub fn color(mut self, color: u32) -> Self {
        self.color = Some(rgb(color));
        self
    }

    /// Tint with an already-resolved color (e.g. a transition frame).
    #[must_use]
    pub const fn color_rgba(mut self, color: Rgba) -> Self {
        self.color = Some(color);
        self
    }
}

impl RenderOnce for Icon {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let path = format!("icons/{}/{}.svg", self.style.dir(), self.name);
        let side = px(self.side.unwrap_or_else(|| self.size.px()));
        // gpui reads an svg's color from the element's own refinement, never
        // from an ancestor, so a glyph without one is silently skipped
        let color = self
            .color
            .unwrap_or_else(|| rgb(crate::ActiveTheme::theme(cx).colors.text));
        // gradient glyphs carry their fill; `svg` renders monochrome, so they
        // go through the full-color `img` pipeline instead
        match self.style {
            Style::Gradient => img(path).size(side).flex_none().into_any_element(),
            Style::Solid | Style::Regular | Style::Light => svg()
                .path(path)
                .size(side)
                .flex_none()
                .text_color(color)
                .into_any_element(),
        }
    }
}