Add DecoratedIcon (#11512)

This allows us to create icons with dynamic decorations drawn on top
like these:


![image](https://github.com/zed-industries/zed/assets/1714999/1d1a22df-8f90-47f2-abbd-ed7afa8fc641)

### Examples:

```rust
div()
    .child(DecoratedIcon::new(
        Icon::new(IconName::Bell).color(Color::Muted),
        IconDecoration::IndicatorDot,
    ))
    .child(
        DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::IndicatorDot)
            .decoration_color(Color::Accent),
    )
    .child(DecoratedIcon::new(
        Icon::new(IconName::Bell).color(Color::Muted),
        IconDecoration::Strikethrough,
    ))
    .child(
        DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::X)
            .decoration_color(Color::Error),
    )
```

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2024-05-07 16:36:13 -04:00 committed by GitHub
parent 768b63a497
commit 47ca343803
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 118 additions and 1 deletions

View file

@ -41,6 +41,17 @@ impl RenderOnce for AnyIcon {
}
}
/// The decoration for an icon.
///
/// For example, this can show an indicator, an "x",
/// or a diagonal strkethrough to indicate something is disabled.
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
pub enum IconDecoration {
Strikethrough,
IndicatorDot,
X,
}
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconSize {
Indicator,
@ -119,6 +130,8 @@ pub enum IconName {
FolderX,
Github,
Hash,
Indicator,
IndicatorX,
InlayHint,
Link,
MagicWand,
@ -159,6 +172,7 @@ pub enum IconName {
SupermavenDisabled,
SupermavenError,
SupermavenInit,
Strikethrough,
Tab,
Terminal,
Trash,
@ -229,6 +243,8 @@ impl IconName {
IconName::FolderX => "icons/stop_sharing.svg",
IconName::Github => "icons/github.svg",
IconName::Hash => "icons/hash.svg",
IconName::Indicator => "icons/indicator.svg",
IconName::IndicatorX => "icons/indicator_x.svg",
IconName::InlayHint => "icons/inlay_hint.svg",
IconName::Link => "icons/link.svg",
IconName::MagicWand => "icons/magic_wand.svg",
@ -269,6 +285,7 @@ impl IconName {
IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
IconName::SupermavenError => "icons/supermaven_error.svg",
IconName::SupermavenInit => "icons/supermaven_init.svg",
IconName::Strikethrough => "icons/strikethrough.svg",
IconName::Tab => "icons/tab.svg",
IconName::Terminal => "icons/terminal.svg",
IconName::Trash => "icons/trash.svg",
@ -344,6 +361,80 @@ impl RenderOnce for Icon {
}
}
#[derive(IntoElement)]
pub struct DecoratedIcon {
icon: Icon,
decoration: IconDecoration,
decoration_color: Color,
parent_background: Option<Hsla>,
}
impl DecoratedIcon {
pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
Self {
icon,
decoration,
decoration_color: Color::Default,
parent_background: None,
}
}
pub fn decoration_color(mut self, color: Color) -> Self {
self.decoration_color = color;
self
}
pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
self.parent_background = background;
self
}
}
impl RenderOnce for DecoratedIcon {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let background = self
.parent_background
.unwrap_or(cx.theme().colors().background);
let size = self.icon.size;
let decoration_icon = match self.decoration {
IconDecoration::Strikethrough => IconName::Strikethrough,
IconDecoration::IndicatorDot => IconName::Indicator,
IconDecoration::X => IconName::IndicatorX,
};
let decoration_svg = |icon: IconName| {
svg()
.absolute()
.top_0()
.left_0()
.path(icon.path())
.size(size)
.flex_none()
.text_color(self.decoration_color.color(cx))
};
let decoration_knockout = |icon: IconName| {
svg()
.absolute()
.top(-rems_from_px(2.))
.left(-rems_from_px(3.))
.path(icon.path())
.size(size + rems_from_px(2.))
.flex_none()
.text_color(background)
};
div()
.relative()
.size(self.icon.size)
.child(self.icon)
.child(decoration_knockout(decoration_icon))
.child(decoration_svg(decoration_icon))
}
}
#[derive(IntoElement)]
pub struct IconWithIndicator {
icon: Icon,

View file

@ -2,7 +2,7 @@ use gpui::Render;
use story::Story;
use strum::IntoEnumIterator;
use crate::prelude::*;
use crate::{prelude::*, DecoratedIcon, IconDecoration};
use crate::{Icon, IconName};
pub struct IconStory;
@ -13,6 +13,23 @@ impl Render for IconStory {
Story::container()
.child(Story::title_for::<Icon>())
.child(Story::label("DecoratedIcon"))
.child(DecoratedIcon::new(
Icon::new(IconName::Bell).color(Color::Muted),
IconDecoration::IndicatorDot,
))
.child(
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::IndicatorDot)
.decoration_color(Color::Accent),
)
.child(DecoratedIcon::new(
Icon::new(IconName::Bell).color(Color::Muted),
IconDecoration::Strikethrough,
))
.child(
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::X)
.decoration_color(Color::Error),
)
.child(Story::label("All Icons"))
.child(div().flex().gap_3().children(icons.map(Icon::new)))
}