318 lines · 9.2 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::{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.    Svg(SharedString),    /// An image file located at the specified path.    ///    /// Currently our SVG renderer is missing support for the following features:    /// 1. Loading SVGs from external files.    /// 2. Rendering polychrome SVGs.    ///    /// In order to support icon themes, we render the icons as images instead.    Image(Arc<Path>),} impl IconSource {    fn from_path(path: impl Into<SharedString>) -> Self {        let path = path.into();        if path.starts_with("icons/") {            Self::Svg(path)        } else {            Self::Image(Arc::from(PathBuf::from(path.as_ref())))        }    }} #[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::Svg(icon.path().into()),            color: Color::default(),            size: IconSize::default().rems(),            transformation: Transformation::default(),        }    }     pub fn from_path(path: impl Into<SharedString>) -> Self {        Self {            source: IconSource::from_path(path),            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    }     pub 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::Svg(path) => svg()                .with_transformation(self.transformation)                .size(self.size)                .flex_none()                .path(path)                .text_color(self.color.color(cx))                .into_any_element(),            IconSource::Image(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("Default", Icon::new(IconName::Star).into_any_element()),                            single_example(                                "Small",                                Icon::new(IconName::Star)                                    .size(IconSize::Small)                                    .into_any_element(),                            ),                            single_example(                                "Large",                                Icon::new(IconName::Star)                                    .size(IconSize::XLarge)                                    .into_any_element(),                            ),                        ],                    ),                    example_group_with_title(                        "Colors",                        vec![                            single_example("Default", Icon::new(IconName::Bell).into_any_element()),                            single_example(                                "Custom Color",                                Icon::new(IconName::Bell)                                    .color(Color::Error)                                    .into_any_element(),                            ),                        ],                    ),                ])                .into_any_element(),        )    }}