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

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.5 15C13.433 15 15 13.433 15 11.5C15 9.567 13.433 8 11.5 8C9.567 8 8 9.567 8 11.5C8 13.433 9.567 15 11.5 15Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 240 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4662 14.9152C13.5801 15.0291 13.7648 15.0291 13.8787 14.9152L14.9145 13.8793C15.0284 13.7654 15.0284 13.5807 14.9145 13.4667L12.9483 11.5004L14.9145 9.53392C15.0285 9.42004 15.0285 9.23533 14.9145 9.12137L13.8787 8.08547C13.7648 7.97154 13.5801 7.97154 13.4662 8.08547L11.5 10.0519L9.53376 8.08545C9.41988 7.97152 9.23517 7.97152 9.12124 8.08545L8.08543 9.12136C7.97152 9.23533 7.97152 9.42004 8.08543 9.53392L10.0517 11.5004L8.08545 13.4667C7.97155 13.5807 7.97155 13.7654 8.08545 13.8793L9.12126 14.9152C9.23517 15.0292 9.41988 15.0292 9.53376 14.9152L11.5 12.9489L13.4662 14.9152Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 756 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4L13 12" stroke="black" stroke-width="2" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 181 B

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)))
}