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), } 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 for AnyIcon { fn from(value: Icon) -> Self { Self::Icon(value) } } impl From> for AnyIcon { fn from(value: AnimationElement) -> 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 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), } impl IconSource { fn from_path(path: impl Into) -> 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) -> 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_border_color: Option, } impl IconWithIndicator { pub fn new(icon: Icon, indicator: Option) -> Self { Self { icon, indicator, indicator_border_color: None, } } pub fn indicator(mut self, indicator: Option) -> 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) -> 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 { 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(), ) } }