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 position: Point<Pixels>,
67 group_name: Option<SharedString>,
68}
69
70impl IconDecoration {
71 /// Creates a new [`IconDecoration`].
72 pub fn new(kind: IconDecorationKind, knockout_color: Hsla, cx: &App) -> Self {
73 let color = cx.theme().colors().icon;
74 let position = Point::default();
75
76 Self {
77 kind,
78 color,
79 knockout_color,
80 knockout_hover_color: knockout_color,
81 position,
82 group_name: None,
83 }
84 }
85
86 /// Sets the kind of decoration.
87 pub fn kind(mut self, kind: IconDecorationKind) -> Self {
88 self.kind = kind;
89 self
90 }
91
92 /// Sets the color of the decoration.
93 pub fn color(mut self, color: Hsla) -> Self {
94 self.color = color;
95 self
96 }
97
98 /// Sets the color of the decoration's knockout
99 ///
100 /// Match this to the background of the element the icon will be rendered
101 /// on.
102 pub fn knockout_color(mut self, color: Hsla) -> Self {
103 self.knockout_color = color;
104 self
105 }
106
107 /// Sets the color of the decoration that is used on hover.
108 pub fn knockout_hover_color(mut self, color: Hsla) -> Self {
109 self.knockout_hover_color = color;
110 self
111 }
112
113 /// Sets the position of the decoration.
114 pub fn position(mut self, position: Point<Pixels>) -> Self {
115 self.position = position;
116 self
117 }
118
119 /// Sets the name of the group the decoration belongs to
120 pub fn group_name(mut self, name: Option<SharedString>) -> Self {
121 self.group_name = name;
122 self
123 }
124}
125
126impl RenderOnce for IconDecoration {
127 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
128 let foreground = svg()
129 .absolute()
130 .bottom_0()
131 .right_0()
132 .size(ICON_DECORATION_SIZE)
133 .path(self.kind.fg().path())
134 .text_color(self.color);
135
136 let background = svg()
137 .absolute()
138 .bottom_0()
139 .right_0()
140 .size(ICON_DECORATION_SIZE)
141 .path(self.kind.bg().path())
142 .text_color(self.knockout_color)
143 .map(|this| match self.group_name {
144 Some(group_name) => this.group_hover(group_name, |style| {
145 style.text_color(self.knockout_hover_color)
146 }),
147 None => this.hover(|style| style.text_color(self.knockout_hover_color)),
148 });
149
150 div()
151 .size(ICON_DECORATION_SIZE)
152 .flex_none()
153 .absolute()
154 .bottom(self.position.y)
155 .right(self.position.x)
156 .child(foreground)
157 .child(background)
158 }
159}