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