1use gpui::{Hsla, IntoElement, Point, svg};
2use strum::{EnumIter, EnumString, IntoStaticStr};
3use ui_macros::DerivePathStr;
4
5use crate::prelude::*;
6
7const ICON_DECORATION_SIZE: Pixels = px(11.);
8
9/// An icon silhouette used to knockout the background of an element for an icon
10/// to sit on top of it, emulating a stroke/border.
11#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, DerivePathStr)]
12#[strum(serialize_all = "snake_case")]
13#[path_str(prefix = "icons/knockouts", suffix = ".svg")]
14pub enum KnockoutIconName {
15 XFg,
16 XBg,
17 DotFg,
18 DotBg,
19 TriangleFg,
20 TriangleBg,
21}
22
23#[derive(Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString)]
24pub enum IconDecorationKind {
25 X,
26 Dot,
27 Triangle,
28}
29
30impl IconDecorationKind {
31 fn fg(&self) -> KnockoutIconName {
32 match self {
33 Self::X => KnockoutIconName::XFg,
34 Self::Dot => KnockoutIconName::DotFg,
35 Self::Triangle => KnockoutIconName::TriangleFg,
36 }
37 }
38
39 fn bg(&self) -> KnockoutIconName {
40 match self {
41 Self::X => KnockoutIconName::XBg,
42 Self::Dot => KnockoutIconName::DotBg,
43 Self::Triangle => KnockoutIconName::TriangleBg,
44 }
45 }
46}
47
48/// The decoration for an icon.
49///
50/// For example, this can show an indicator, an "x", or a diagonal strikethrough
51/// to indicate something is disabled.
52#[derive(IntoElement)]
53pub struct IconDecoration {
54 kind: IconDecorationKind,
55 color: Hsla,
56 knockout_color: Hsla,
57 knockout_hover_color: Hsla,
58 position: Point<Pixels>,
59 group_name: Option<SharedString>,
60}
61
62impl IconDecoration {
63 /// Creates a new [`IconDecoration`].
64 pub fn new(kind: IconDecorationKind, knockout_color: Hsla, cx: &App) -> Self {
65 let color = cx.theme().colors().icon;
66 let position = Point::default();
67
68 Self {
69 kind,
70 color,
71 knockout_color,
72 knockout_hover_color: knockout_color,
73 position,
74 group_name: None,
75 }
76 }
77
78 /// Sets the kind of decoration.
79 pub fn kind(mut self, kind: IconDecorationKind) -> Self {
80 self.kind = kind;
81 self
82 }
83
84 /// Sets the color of the decoration.
85 pub fn color(mut self, color: Hsla) -> Self {
86 self.color = color;
87 self
88 }
89
90 /// Sets the color of the decoration's knockout
91 ///
92 /// Match this to the background of the element the icon will be rendered
93 /// on.
94 pub fn knockout_color(mut self, color: Hsla) -> Self {
95 self.knockout_color = color;
96 self
97 }
98
99 /// Sets the color of the decoration that is used on hover.
100 pub fn knockout_hover_color(mut self, color: Hsla) -> Self {
101 self.knockout_hover_color = color;
102 self
103 }
104
105 /// Sets the position of the decoration.
106 pub fn position(mut self, position: Point<Pixels>) -> Self {
107 self.position = position;
108 self
109 }
110
111 /// Sets the name of the group the decoration belongs to
112 pub fn group_name(mut self, name: Option<SharedString>) -> Self {
113 self.group_name = name;
114 self
115 }
116}
117
118impl RenderOnce for IconDecoration {
119 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
120 let foreground = svg()
121 .absolute()
122 .bottom_0()
123 .right_0()
124 .size(ICON_DECORATION_SIZE)
125 .path(self.kind.fg().path())
126 .text_color(self.color);
127
128 let background = svg()
129 .absolute()
130 .bottom_0()
131 .right_0()
132 .size(ICON_DECORATION_SIZE)
133 .path(self.kind.bg().path())
134 .text_color(self.knockout_color)
135 .map(|this| match self.group_name {
136 Some(group_name) => this.group_hover(group_name, |style| {
137 style.text_color(self.knockout_hover_color)
138 }),
139 None => this.hover(|style| style.text_color(self.knockout_hover_color)),
140 });
141
142 div()
143 .size(ICON_DECORATION_SIZE)
144 .flex_none()
145 .absolute()
146 .bottom(self.position.y)
147 .right(self.position.x)
148 .child(foreground)
149 .child(background)
150 }
151}