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