From 4739797e5d3585ee263410d4225b61dfb4735349 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 May 2024 17:52:26 -0400 Subject: [PATCH] Add the ability to render icons as indicators (#11273) This PR adds the ability to render `Icon`s as an `Indicator`. Release Notes: - N/A Co-authored-by: Nate Butler --- crates/gpui/src/elements/animation.rs | 9 ++++ crates/ui/src/components/icon.rs | 56 +++++++++++++++++++-- crates/ui/src/components/indicator.rs | 70 +++++++++++++++++++++------ 3 files changed, 114 insertions(+), 21 deletions(-) diff --git a/crates/gpui/src/elements/animation.rs b/crates/gpui/src/elements/animation.rs index 34806d962379965b6dee1e6b96445d9671473e37..29506a622432e7a71da2d29ddb650fc5851ffa2e 100644 --- a/crates/gpui/src/elements/animation.rs +++ b/crates/gpui/src/elements/animation.rs @@ -72,6 +72,15 @@ pub struct AnimationElement { animator: Box E + 'static>, } +impl AnimationElement { + /// Returns a new [`AnimationElement`] after applying the given function + /// to the element being animated. + pub fn map_element(mut self, f: impl FnOnce(E) -> E) -> AnimationElement { + self.element = self.element.map(f); + self + } +} + impl IntoElement for AnimationElement { type Element = AnimationElement; diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 676d1b4826f8b5be192c68d39c865424a7eb8b13..369a14a3bac255fc6d9e4e35fe1fe59937374983 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -1,8 +1,46 @@ -use gpui::{svg, Hsla, IntoElement, Rems, Transformation}; +use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation}; use strum::EnumIter; use crate::{prelude::*, Indicator}; +#[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, _cx: &mut WindowContext) -> 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 { Indicator, @@ -236,7 +274,7 @@ impl IconName { pub struct Icon { path: SharedString, color: Color, - size: IconSize, + size: Rems, transformation: Transformation, } @@ -245,7 +283,7 @@ impl Icon { Self { path: icon.path().into(), color: Color::default(), - size: IconSize::default(), + size: IconSize::default().rems(), transformation: Transformation::default(), } } @@ -254,7 +292,7 @@ impl Icon { Self { path: path.into(), color: Color::default(), - size: IconSize::default(), + size: IconSize::default().rems(), transformation: Transformation::default(), } } @@ -265,6 +303,14 @@ impl Icon { } 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 } @@ -279,7 +325,7 @@ impl RenderOnce for Icon { fn render(self, cx: &mut WindowContext) -> impl IntoElement { svg() .with_transformation(self.transformation) - .size(self.size.rems()) + .size(self.size) .flex_none() .path(self.path) .text_color(self.color.color(cx)) diff --git a/crates/ui/src/components/indicator.rs b/crates/ui/src/components/indicator.rs index 5c548b26f2a250c92f5b10567368b4740da67829..174bd4b35d4e4cc8026032bc474b420a91b699c1 100644 --- a/crates/ui/src/components/indicator.rs +++ b/crates/ui/src/components/indicator.rs @@ -1,17 +1,17 @@ -use gpui::Position; +use gpui::Transformation; -use crate::prelude::*; +use crate::{prelude::*, AnyIcon}; #[derive(Default)] pub enum IndicatorStyle { #[default] Dot, Bar, + Icon(AnyIcon), } #[derive(IntoElement)] pub struct Indicator { - position: Position, style: IndicatorStyle, pub color: Color, } @@ -19,7 +19,6 @@ pub struct Indicator { impl Indicator { pub fn dot() -> Self { Self { - position: Position::Relative, style: IndicatorStyle::Dot, color: Color::Default, } @@ -27,32 +26,71 @@ impl Indicator { pub fn bar() -> Self { Self { - position: Position::Relative, style: IndicatorStyle::Dot, color: Color::Default, } } + pub fn icon(icon: impl Into) -> Self { + Self { + style: IndicatorStyle::Icon(icon.into()), + color: Color::Default, + } + } + pub fn color(mut self, color: Color) -> Self { self.color = color; self } +} + +impl RenderOnce for Indicator { + fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let container = div().flex_none(); + + match self.style { + IndicatorStyle::Icon(icon) => container + .child(icon.map(|icon| icon.custom_size(rems_from_px(8.)).color(self.color))), + IndicatorStyle::Dot => container + .w_1p5() + .h_1p5() + .rounded_full() + .bg(self.color.color(cx)), + IndicatorStyle::Bar => container + .w_full() + .h_1p5() + .rounded_t_md() + .bg(self.color.color(cx)), + } + } +} + +#[derive(IntoElement)] +pub struct IndicatorIcon { + icon: Icon, + transformation: Option, +} - pub fn absolute(mut self) -> Self { - self.position = Position::Absolute; +impl IndicatorIcon { + pub fn new(icon: Icon) -> Self { + Self { + icon, + transformation: None, + } + } + + pub fn transformation(mut self, transformation: Transformation) -> Self { + self.transformation = Some(transformation); self } } -impl RenderOnce for Indicator { - fn render(self, cx: &mut WindowContext) -> impl IntoElement { - div() - .flex_none() - .map(|this| match self.style { - IndicatorStyle::Dot => this.w_1p5().h_1p5().rounded_full(), - IndicatorStyle::Bar => this.w_full().h_1p5().rounded_t_md(), +impl RenderOnce for IndicatorIcon { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + self.icon + .custom_size(rems_from_px(8.)) + .when_some(self.transformation, |this, transformation| { + this.transform(transformation) }) - .when(self.position == Position::Absolute, |this| this.absolute()) - .bg(self.color.color(cx)) } }