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 <nate@zed.dev>
This commit is contained in:
Marshall Bowers 2024-05-01 17:52:26 -04:00 committed by GitHub
parent 4f5312804d
commit 4739797e5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 116 additions and 23 deletions

View File

@ -72,6 +72,15 @@ pub struct AnimationElement<E> {
animator: Box<dyn Fn(E, f32) -> E + 'static>,
}
impl<E> AnimationElement<E> {
/// Returns a new [`AnimationElement<E>`] after applying the given function
/// to the element being animated.
pub fn map_element(mut self, f: impl FnOnce(E) -> E) -> AnimationElement<E> {
self.element = self.element.map(f);
self
}
}
impl<E: IntoElement + 'static> IntoElement for AnimationElement<E> {
type Element = AnimationElement<E>;

View File

@ -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<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, _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))

View File

@ -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<AnyIcon>) -> Self {
Self {
style: IndicatorStyle::Icon(icon.into()),
color: Color::Default,
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn absolute(mut self) -> Self {
self.position = Position::Absolute;
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(),
})
.when(self.position == Position::Absolute, |this| this.absolute())
.bg(self.color.color(cx))
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<Transformation>,
}
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 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)
})
}
}