369 lines · 11.9 KB · Rust
RawDownload
mod decorated_icon;mod icon_decoration; use std::path::{Path, PathBuf};use std::sync::Arc; pub use decorated_icon::*;use gpui::{AnimationElement, AnyElement, Hsla, IntoElement, Rems, Transformation, img, svg};pub use icon_decoration::*;pub use icons::*; use crate::traits::transformable::Transformable;use crate::{Indicator, prelude::*}; #[derive(IntoElement)]pub enum AnyIcon {    Icon(Icon),    AnimatedIcon(AnimationElement<Icon>),} impl AnyIcon {    /// Returns a new [`AnyIcon`] after applying the given mapping function    /// to the contained [`Icon`].    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {        match self {            Self::Icon(icon) => Self::Icon(f(icon)),            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),        }    }} impl From<Icon> for AnyIcon {    fn from(value: Icon) -> Self {        Self::Icon(value)    }} impl From<AnimationElement<Icon>> for AnyIcon {    fn from(value: AnimationElement<Icon>) -> Self {        Self::AnimatedIcon(value)    }} impl RenderOnce for AnyIcon {    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {        match self {            Self::Icon(icon) => icon.into_any_element(),            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),        }    }} #[derive(Default, PartialEq, Copy, Clone)]pub enum IconSize {    /// 10px    Indicator,    /// 12px    XSmall,    /// 14px    Small,    #[default]    /// 16px    Medium,    /// 48px    XLarge,    Custom(Rems),} impl IconSize {    pub fn rems(self) -> Rems {        match self {            IconSize::Indicator => rems_from_px(10.),            IconSize::XSmall => rems_from_px(12.),            IconSize::Small => rems_from_px(14.),            IconSize::Medium => rems_from_px(16.),            IconSize::XLarge => rems_from_px(48.),            IconSize::Custom(size) => size,        }    }     /// Returns the individual components of the square that contains this [`IconSize`].    ///    /// The returned tuple contains:    ///   1. The length of one side of the square    ///   2. The padding of one side of the square    pub fn square_components(&self, window: &mut Window, cx: &mut App) -> (Pixels, Pixels) {        let icon_size = self.rems() * window.rem_size();        let padding = match self {            IconSize::Indicator => DynamicSpacing::Base00.px(cx),            IconSize::XSmall => DynamicSpacing::Base02.px(cx),            IconSize::Small => DynamicSpacing::Base02.px(cx),            IconSize::Medium => DynamicSpacing::Base02.px(cx),            IconSize::XLarge => DynamicSpacing::Base02.px(cx),            // TODO: Wire into dynamic spacing            IconSize::Custom(size) => size.to_pixels(window.rem_size()),        };         (icon_size, padding)    }     /// Returns the length of a side of the square that contains this [`IconSize`], with padding.    pub fn square(&self, window: &mut Window, cx: &mut App) -> Pixels {        let (icon_size, padding) = self.square_components(window, cx);         icon_size + padding * 2.    }} impl From<IconName> for Icon {    fn from(icon: IconName) -> Self {        Icon::new(icon)    }} /// The source of an icon.enum IconSource {    /// An SVG embedded in the Zed binary.    Embedded(SharedString),    /// An image file located at the specified path.    ///    /// Currently our SVG renderer is missing support for rendering polychrome SVGs.    ///    /// In order to support icon themes, we render the icons as images instead.    External(Arc<Path>),    /// An SVG not embedded in the Zed binary.    ExternalSvg(SharedString),} #[derive(IntoElement, RegisterComponent)]pub struct Icon {    source: IconSource,    color: Color,    size: Rems,    transformation: Transformation,} impl Icon {    pub fn new(icon: IconName) -> Self {        Self {            source: IconSource::Embedded(icon.path().into()),            color: Color::default(),            size: IconSize::default().rems(),            transformation: Transformation::default(),        }    }     /// Create an icon from a path. Uses a heuristic to determine if it's embedded or external:    /// - Paths starting with "icons/" are treated as embedded SVGs    /// - Other paths are treated as external raster images (from icon themes)    pub fn from_path(path: impl Into<SharedString>) -> Self {        let path = path.into();        let source = if path.starts_with("icons/") {            IconSource::Embedded(path)        } else {            IconSource::External(Arc::from(PathBuf::from(path.as_ref())))        };        Self {            source,            color: Color::default(),            size: IconSize::default().rems(),            transformation: Transformation::default(),        }    }     pub fn from_external_svg(svg: SharedString) -> Self {        Self {            source: IconSource::ExternalSvg(svg),            color: Color::default(),            size: IconSize::default().rems(),            transformation: Transformation::default(),        }    }     pub fn color(mut self, color: Color) -> Self {        self.color = color;        self    }     pub fn size(mut self, size: IconSize) -> Self {        self.size = size.rems();        self    }     /// Sets a custom size for the icon, in [`Rems`].    ///    /// Not to be exposed outside of the `ui` crate.    pub(crate) fn custom_size(mut self, size: Rems) -> Self {        self.size = size;        self    }} impl Transformable for Icon {    fn transform(mut self, transformation: Transformation) -> Self {        self.transformation = transformation;        self    }} impl RenderOnce for Icon {    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {        match self.source {            IconSource::Embedded(path) => svg()                .with_transformation(self.transformation)                .size(self.size)                .flex_none()                .path(path)                .text_color(self.color.color(cx))                .into_any_element(),            IconSource::ExternalSvg(path) => svg()                .external_path(path)                .with_transformation(self.transformation)                .size(self.size)                .flex_none()                .text_color(self.color.color(cx))                .into_any_element(),            IconSource::External(path) => img(path)                .size(self.size)                .flex_none()                .text_color(self.color.color(cx))                .into_any_element(),        }    }} #[derive(IntoElement)]pub struct IconWithIndicator {    icon: Icon,    indicator: Option<Indicator>,    indicator_border_color: Option<Hsla>,} impl IconWithIndicator {    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {        Self {            icon,            indicator,            indicator_border_color: None,        }    }     pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {        self.indicator = indicator;        self    }     pub fn indicator_color(mut self, color: Color) -> Self {        if let Some(indicator) = self.indicator.as_mut() {            indicator.color = color;        }        self    }     pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {        self.indicator_border_color = color;        self    }} impl RenderOnce for IconWithIndicator {    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {        let indicator_border_color = self            .indicator_border_color            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);         div()            .relative()            .child(self.icon)            .when_some(self.indicator, |this, indicator| {                this.child(                    div()                        .absolute()                        .size_2p5()                        .border_2()                        .border_color(indicator_border_color)                        .rounded_full()                        .bottom_neg_0p5()                        .right_neg_0p5()                        .child(indicator),                )            })    }} impl Component for Icon {    fn scope() -> ComponentScope {        ComponentScope::Images    }     fn description() -> Option<&'static str> {        Some(            "A versatile icon component that supports SVG and image-based icons with customizable size, color, and transformations.",        )    }     fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {        Some(            v_flex()                .gap_6()                .children(vec![                    example_group_with_title(                        "Sizes",                        vec![single_example(                            "XSmall, Small, Default, Large",                            h_flex()                                .gap_1()                                .child(                                    Icon::new(IconName::Star)                                        .size(IconSize::XSmall)                                        .into_any_element(),                                )                                .child(                                    Icon::new(IconName::Star)                                        .size(IconSize::Small)                                        .into_any_element(),                                )                                .child(Icon::new(IconName::Star).into_any_element())                                .child(                                    Icon::new(IconName::Star)                                        .size(IconSize::XLarge)                                        .into_any_element(),                                )                                .into_any_element(),                        )],                    ),                    example_group_with_title(                        "Colors",                        vec![single_example(                            "Default & Custom",                            h_flex()                                .gap_1()                                .child(Icon::new(IconName::Star).into_any_element())                                .child(                                    Icon::new(IconName::Star)                                        .color(Color::Error)                                        .into_any_element(),                                )                                .into_any_element(),                        )],                    ),                    example_group_with_title(                        "All Icons",                        vec![single_example(                            "All Icons",                            h_flex()                                .image_cache(gpui::retain_all("all icons"))                                .flex_wrap()                                .gap_2()                                .children(<IconName as strum::IntoEnumIterator>::iter().map(                                    |icon_name| {                                        h_flex()                                            .p_1()                                            .gap_1()                                            .border_1()                                            .border_color(cx.theme().colors().border_variant)                                            .bg(cx.theme().colors().element_disabled)                                            .rounded_sm()                                            .child(Icon::new(icon_name).into_any_element())                                            .child(SharedString::new_static(icon_name.into()))                                    },                                ))                                .into_any_element(),                        )],                    ),                ])                .into_any_element(),        )    }}