diff --git a/assets/icons/knockouts/dot_bg.svg b/assets/icons/knockouts/dot_bg.svg
new file mode 100644
index 0000000000..9f5ba034e2
--- /dev/null
+++ b/assets/icons/knockouts/dot_bg.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/knockouts/dot_fg.svg b/assets/icons/knockouts/dot_fg.svg
new file mode 100644
index 0000000000..54eaacbfa9
--- /dev/null
+++ b/assets/icons/knockouts/dot_fg.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/knockouts/triangle_bg.svg b/assets/icons/knockouts/triangle_bg.svg
new file mode 100644
index 0000000000..b0c5ae6e77
--- /dev/null
+++ b/assets/icons/knockouts/triangle_bg.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/knockouts/triangle_fg.svg b/assets/icons/knockouts/triangle_fg.svg
new file mode 100644
index 0000000000..f8f8b8c2bc
--- /dev/null
+++ b/assets/icons/knockouts/triangle_fg.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/icons/knockouts/x_bg.svg b/assets/icons/knockouts/x_bg.svg
new file mode 100644
index 0000000000..0bc5059e73
--- /dev/null
+++ b/assets/icons/knockouts/x_bg.svg
@@ -0,0 +1,10 @@
+
diff --git a/assets/icons/knockouts/x_fg.svg b/assets/icons/knockouts/x_fg.svg
new file mode 100644
index 0000000000..a3d47f1373
--- /dev/null
+++ b/assets/icons/knockouts/x_fg.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs
index 26f30f5588..fdf9b537bc 100644
--- a/crates/ui/src/components/button/button.rs
+++ b/crates/ui/src/components/button/button.rs
@@ -445,7 +445,7 @@ impl ComponentPreview for Button {
"A button allows users to take actions, and make choices, with a single tap."
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
vec![
example_group_with_title(
"Styles",
diff --git a/crates/ui/src/components/checkbox.rs b/crates/ui/src/components/checkbox.rs
index efa907ea20..0a3fc6f650 100644
--- a/crates/ui/src/components/checkbox.rs
+++ b/crates/ui/src/components/checkbox.rs
@@ -118,7 +118,7 @@ impl ComponentPreview for Checkbox {
"A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
vec![
example_group_with_title(
"Default",
@@ -214,7 +214,7 @@ impl ComponentPreview for CheckboxWithLabel {
"A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
vec![example_group(vec![
single_example(
"Unselected",
diff --git a/crates/ui/src/components/facepile.rs b/crates/ui/src/components/facepile.rs
index 5d406f67c7..eb4dd8a98e 100644
--- a/crates/ui/src/components/facepile.rs
+++ b/crates/ui/src/components/facepile.rs
@@ -67,7 +67,7 @@ impl ComponentPreview for Facepile {
\n\nFacepiles are used to display a group of people or things,\
such as a list of participants in a collaboration session."
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
let few_faces: [&'static str; 3] = [
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index c6c92ee9d9..89763c3a42 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -1,7 +1,7 @@
#![allow(missing_docs)]
-use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
+use gpui::{svg, AnimationElement, Hsla, IntoElement, Point, Rems, Transformation};
use serde::{Deserialize, Serialize};
-use strum::{EnumIter, EnumString, IntoStaticStr};
+use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
use ui_macros::DerivePathStr;
use crate::{
@@ -48,17 +48,6 @@ impl RenderOnce for AnyIcon {
}
}
-/// The decoration for an icon.
-///
-/// For example, this can show an indicator, an "x",
-/// or a diagonal strikethrough 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 {
/// 10px
@@ -367,77 +356,233 @@ impl RenderOnce for Icon {
}
}
-#[derive(IntoElement)]
-pub struct DecoratedIcon {
- icon: Icon,
- decoration: IconDecoration,
- decoration_color: Color,
- parent_background: Option,
+const ICON_DECORATION_SIZE: f32 = 11.0;
+
+/// An icon silhouette used to knockout the background of an element
+/// for an icon to sit on top of it, emulating a stroke/border.
+#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, DerivePathStr)]
+#[strum(serialize_all = "snake_case")]
+#[path_str(prefix = "icons/knockouts", suffix = ".svg")]
+pub enum KnockoutIconName {
+ // /icons/knockouts/x1.svg
+ XFg,
+ XBg,
+ DotFg,
+ DotBg,
+ TriangleFg,
+ TriangleBg,
}
-impl DecoratedIcon {
- pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
- Self {
- icon,
- decoration,
- decoration_color: Color::Default,
- parent_background: None,
+#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString)]
+pub enum IconDecorationKind {
+ // Slash,
+ X,
+ Dot,
+ Triangle,
+}
+
+impl IconDecorationKind {
+ fn fg(&self) -> KnockoutIconName {
+ match self {
+ Self::X => KnockoutIconName::XFg,
+ Self::Dot => KnockoutIconName::DotFg,
+ Self::Triangle => KnockoutIconName::TriangleFg,
}
}
- pub fn decoration_color(mut self, color: Color) -> Self {
- self.decoration_color = color;
+ fn bg(&self) -> KnockoutIconName {
+ match self {
+ Self::X => KnockoutIconName::XBg,
+ Self::Dot => KnockoutIconName::DotBg,
+ Self::Triangle => KnockoutIconName::TriangleBg,
+ }
+ }
+}
+
+/// The decoration for an icon.
+///
+/// For example, this can show an indicator, an "x",
+/// or a diagonal strikethrough to indicate something is disabled.
+#[derive(IntoElement)]
+pub struct IconDecoration {
+ kind: IconDecorationKind,
+ color: Hsla,
+ knockout_color: Hsla,
+ position: Point,
+}
+
+impl IconDecoration {
+ /// Create a new icon decoration
+ pub fn new(kind: IconDecorationKind, knockout_color: Hsla, cx: &WindowContext) -> Self {
+ let color = cx.theme().colors().icon;
+ let position = Point::default();
+
+ Self {
+ kind,
+ color,
+ knockout_color,
+ position,
+ }
+ }
+
+ /// Sets the kind of decoration
+ pub fn kind(mut self, kind: IconDecorationKind) -> Self {
+ self.kind = kind;
self
}
- pub fn parent_background(mut self, background: Option) -> Self {
- self.parent_background = background;
+ /// Sets the color of the decoration
+ pub fn color(mut self, color: Hsla) -> Self {
+ self.color = color;
self
}
+
+ /// Sets the color of the decoration's knockout
+ ///
+ /// Match this to the background of the element
+ /// the icon will be rendered on
+ pub fn knockout_color(mut self, color: Hsla) -> Self {
+ self.knockout_color = color;
+ self
+ }
+
+ /// Sets the position of the decoration
+ pub fn position(mut self, position: Point) -> Self {
+ self.position = position;
+ self
+ }
+}
+
+impl RenderOnce for IconDecoration {
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
+ div()
+ .size(px(ICON_DECORATION_SIZE))
+ .flex_none()
+ .absolute()
+ .bottom(self.position.y)
+ .right(self.position.x)
+ .child(
+ // foreground
+ svg()
+ .absolute()
+ .bottom_0()
+ .right_0()
+ .size(px(ICON_DECORATION_SIZE))
+ .path(self.kind.fg().path())
+ .text_color(self.color),
+ )
+ .child(
+ // background
+ svg()
+ .absolute()
+ .bottom_0()
+ .right_0()
+ .size(px(ICON_DECORATION_SIZE))
+ .path(self.kind.bg().path())
+ .text_color(self.knockout_color),
+ )
+ }
+}
+
+impl ComponentPreview for IconDecoration {
+ fn examples(cx: &WindowContext) -> Vec> {
+ let all_kinds = IconDecorationKind::iter().collect::>();
+
+ let examples = all_kinds
+ .iter()
+ .map(|kind| {
+ let name = format!("{:?}", kind).to_string();
+
+ single_example(
+ name,
+ IconDecoration::new(*kind, cx.theme().colors().surface_background, cx),
+ )
+ })
+ .collect();
+
+ vec![example_group(examples)]
+ }
+}
+
+#[derive(IntoElement)]
+pub struct DecoratedIcon {
+ icon: Icon,
+ decoration: Option,
+}
+
+impl DecoratedIcon {
+ pub fn new(icon: Icon, decoration: Option) -> Self {
+ Self { icon, decoration }
+ }
}
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)
- };
-
+ fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
div()
.relative()
.size(self.icon.size)
.child(self.icon)
- .child(decoration_knockout(decoration_icon))
- .child(decoration_svg(decoration_icon))
+ .when_some(self.decoration, |this, decoration| this.child(decoration))
+ }
+}
+
+impl ComponentPreview for DecoratedIcon {
+ fn examples(cx: &WindowContext) -> Vec> {
+ let icon_1 = Icon::new(IconName::FileDoc);
+ let icon_2 = Icon::new(IconName::FileDoc);
+ let icon_3 = Icon::new(IconName::FileDoc);
+ let icon_4 = Icon::new(IconName::FileDoc);
+
+ let decoration_x = IconDecoration::new(
+ IconDecorationKind::X,
+ cx.theme().colors().surface_background,
+ cx,
+ )
+ .color(cx.theme().status().error)
+ .position(Point {
+ x: px(-2.),
+ y: px(-2.),
+ });
+
+ let decoration_triangle = IconDecoration::new(
+ IconDecorationKind::Triangle,
+ cx.theme().colors().surface_background,
+ cx,
+ )
+ .color(cx.theme().status().error)
+ .position(Point {
+ x: px(-2.),
+ y: px(-2.),
+ });
+
+ let decoration_dot = IconDecoration::new(
+ IconDecorationKind::Dot,
+ cx.theme().colors().surface_background,
+ cx,
+ )
+ .color(cx.theme().status().error)
+ .position(Point {
+ x: px(-2.),
+ y: px(-2.),
+ });
+
+ let examples = vec![
+ single_example("no_decoration", DecoratedIcon::new(icon_1, None)),
+ single_example(
+ "with_decoration",
+ DecoratedIcon::new(icon_2, Some(decoration_x)),
+ ),
+ single_example(
+ "with_decoration",
+ DecoratedIcon::new(icon_3, Some(decoration_triangle)),
+ ),
+ single_example(
+ "with_decoration",
+ DecoratedIcon::new(icon_4, Some(decoration_dot)),
+ ),
+ ];
+
+ vec![example_group(examples)]
}
}
@@ -501,7 +646,7 @@ impl RenderOnce for IconWithIndicator {
}
impl ComponentPreview for Icon {
- fn examples() -> Vec> {
+ fn examples(_cx: &WindowContext) -> Vec> {
let arrow_icons = vec![
IconName::ArrowDown,
IconName::ArrowLeft,
diff --git a/crates/ui/src/components/indicator.rs b/crates/ui/src/components/indicator.rs
index 8ce075d228..b0d5b0d2da 100644
--- a/crates/ui/src/components/indicator.rs
+++ b/crates/ui/src/components/indicator.rs
@@ -89,7 +89,7 @@ impl ComponentPreview for Indicator {
"An indicator visually represents a status or state."
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
vec![
example_group_with_title(
"Types",
diff --git a/crates/ui/src/components/stories/icon.rs b/crates/ui/src/components/stories/icon.rs
index bdd253b567..618634e153 100644
--- a/crates/ui/src/components/stories/icon.rs
+++ b/crates/ui/src/components/stories/icon.rs
@@ -2,7 +2,7 @@ use gpui::Render;
use story::Story;
use strum::IntoEnumIterator;
-use crate::{prelude::*, DecoratedIcon, IconDecoration};
+use crate::prelude::*;
use crate::{Icon, IconName};
pub struct IconStory;
@@ -14,22 +14,6 @@ impl Render for IconStory {
Story::container()
.child(Story::title_for::())
.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)))
}
diff --git a/crates/ui/src/components/table.rs b/crates/ui/src/components/table.rs
index 59273cce12..0ef5eda7b7 100644
--- a/crates/ui/src/components/table.rs
+++ b/crates/ui/src/components/table.rs
@@ -160,7 +160,7 @@ impl ComponentPreview for Table {
ExampleLabelSide::Top
}
- fn examples() -> Vec> {
+ fn examples(_: &WindowContext) -> Vec> {
vec![
example_group(vec![
single_example(
diff --git a/crates/ui/src/traits/component_preview.rs b/crates/ui/src/traits/component_preview.rs
index 1fece0804a..1cb577a97f 100644
--- a/crates/ui/src/traits/component_preview.rs
+++ b/crates/ui/src/traits/component_preview.rs
@@ -30,10 +30,10 @@ pub trait ComponentPreview: IntoElement {
ExampleLabelSide::default()
}
- fn examples() -> Vec>;
+ fn examples(_cx: &WindowContext) -> Vec>;
- fn component_previews() -> Vec {
- Self::examples()
+ fn component_previews(cx: &WindowContext) -> Vec {
+ Self::examples(cx)
.into_iter()
.map(|example| Self::render_example_group(example))
.collect()
@@ -73,7 +73,7 @@ pub trait ComponentPreview: IntoElement {
)
}),
)
- .children(Self::component_previews())
+ .children(Self::component_previews(cx))
.into_any_element()
}
diff --git a/crates/workspace/src/theme_preview.rs b/crates/workspace/src/theme_preview.rs
index 4788842d4f..fef4dfc86e 100644
--- a/crates/workspace/src/theme_preview.rs
+++ b/crates/workspace/src/theme_preview.rs
@@ -5,7 +5,8 @@ use theme::all_theme_colors;
use ui::{
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
- Checkbox, CheckboxWithLabel, ElevationIndex, Facepile, Indicator, Table, TintColor, Tooltip,
+ Checkbox, CheckboxWithLabel, DecoratedIcon, ElevationIndex, Facepile, IconDecoration,
+ Indicator, Table, TintColor, Tooltip,
};
use crate::{Item, Workspace};
@@ -509,6 +510,8 @@ impl ThemePreview {
.overflow_scroll()
.size_full()
.gap_2()
+ .child(IconDecoration::render_component_previews(cx))
+ .child(DecoratedIcon::render_component_previews(cx))
.child(Checkbox::render_component_previews(cx))
.child(CheckboxWithLabel::render_component_previews(cx))
.child(Facepile::render_component_previews(cx))