1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
2use strum::EnumIter;
3
4use crate::{prelude::*, Indicator};
5
6#[derive(IntoElement)]
7pub enum AnyIcon {
8 Icon(Icon),
9 AnimatedIcon(AnimationElement<Icon>),
10}
11
12impl AnyIcon {
13 /// Returns a new [`AnyIcon`] after applying the given mapping function
14 /// to the contained [`Icon`].
15 pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
16 match self {
17 Self::Icon(icon) => Self::Icon(f(icon)),
18 Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
19 }
20 }
21}
22
23impl From<Icon> for AnyIcon {
24 fn from(value: Icon) -> Self {
25 Self::Icon(value)
26 }
27}
28
29impl From<AnimationElement<Icon>> for AnyIcon {
30 fn from(value: AnimationElement<Icon>) -> Self {
31 Self::AnimatedIcon(value)
32 }
33}
34
35impl RenderOnce for AnyIcon {
36 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
37 match self {
38 Self::Icon(icon) => icon.into_any_element(),
39 Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
40 }
41 }
42}
43
44/// The decoration for an icon.
45///
46/// For example, this can show an indicator, an "x",
47/// or a diagonal strkethrough to indicate something is disabled.
48#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
49pub enum IconDecoration {
50 Strikethrough,
51 IndicatorDot,
52 X,
53}
54
55#[derive(Default, PartialEq, Copy, Clone)]
56pub enum IconSize {
57 Indicator,
58 XSmall,
59 Small,
60 #[default]
61 Medium,
62}
63
64impl IconSize {
65 pub fn rems(self) -> Rems {
66 match self {
67 IconSize::Indicator => rems_from_px(10.),
68 IconSize::XSmall => rems_from_px(12.),
69 IconSize::Small => rems_from_px(14.),
70 IconSize::Medium => rems_from_px(16.),
71 }
72 }
73}
74
75#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
76pub enum IconName {
77 Ai,
78 ArrowDown,
79 ArrowLeft,
80 ArrowRight,
81 ArrowUp,
82 ArrowUpRight,
83 ArrowCircle,
84 AtSign,
85 AudioOff,
86 AudioOn,
87 Backspace,
88 Bell,
89 BellOff,
90 BellRing,
91 BellDot,
92 Bolt,
93 CaseSensitive,
94 Check,
95 ChevronDown,
96 ChevronLeft,
97 ChevronRight,
98 ChevronUp,
99 ExpandVertical,
100 Close,
101 Code,
102 Collab,
103 Command,
104 Control,
105 Copilot,
106 CopilotDisabled,
107 CopilotError,
108 CopilotInit,
109 Copy,
110 Dash,
111 Delete,
112 Disconnected,
113 Ellipsis,
114 Envelope,
115 Escape,
116 ExclamationTriangle,
117 Exit,
118 ExternalLink,
119 File,
120 FileDoc,
121 FileGeneric,
122 FileGit,
123 FileLock,
124 FileRust,
125 FileToml,
126 FileTree,
127 Filter,
128 Folder,
129 FolderOpen,
130 FolderX,
131 Github,
132 Hash,
133 Indicator,
134 IndicatorX,
135 InlayHint,
136 Link,
137 MagicWand,
138 MagnifyingGlass,
139 MailOpen,
140 Maximize,
141 Menu,
142 MessageBubbles,
143 Mic,
144 MicMute,
145 Minimize,
146 Option,
147 PageDown,
148 PageUp,
149 Pencil,
150 Person,
151 Play,
152 Plus,
153 Public,
154 Quote,
155 Regex,
156 Replace,
157 ReplaceAll,
158 ReplaceNext,
159 Return,
160 ReplyArrowRight,
161 Settings,
162 Sliders,
163 Screen,
164 SelectAll,
165 Server,
166 Shift,
167 Snip,
168 Space,
169 Split,
170 Spinner,
171 Supermaven,
172 SupermavenDisabled,
173 SupermavenError,
174 SupermavenInit,
175 Strikethrough,
176 Tab,
177 Terminal,
178 Trash,
179 Update,
180 WholeWord,
181 XCircle,
182 ZedXCopilot,
183 ZedAssistant,
184 PullRequest,
185}
186
187impl IconName {
188 pub fn path(self) -> &'static str {
189 match self {
190 IconName::Ai => "icons/ai.svg",
191 IconName::ArrowDown => "icons/arrow_down.svg",
192 IconName::ArrowLeft => "icons/arrow_left.svg",
193 IconName::ArrowRight => "icons/arrow_right.svg",
194 IconName::ArrowUp => "icons/arrow_up.svg",
195 IconName::ArrowUpRight => "icons/arrow_up_right.svg",
196 IconName::ArrowCircle => "icons/arrow_circle.svg",
197 IconName::AtSign => "icons/at_sign.svg",
198 IconName::AudioOff => "icons/speaker_off.svg",
199 IconName::AudioOn => "icons/speaker_loud.svg",
200 IconName::Backspace => "icons/backspace.svg",
201 IconName::Bell => "icons/bell.svg",
202 IconName::BellOff => "icons/bell_off.svg",
203 IconName::BellRing => "icons/bell_ring.svg",
204 IconName::BellDot => "icons/bell_dot.svg",
205 IconName::Bolt => "icons/bolt.svg",
206 IconName::CaseSensitive => "icons/case_insensitive.svg",
207 IconName::Check => "icons/check.svg",
208 IconName::ChevronDown => "icons/chevron_down.svg",
209 IconName::ChevronLeft => "icons/chevron_left.svg",
210 IconName::ChevronRight => "icons/chevron_right.svg",
211 IconName::ChevronUp => "icons/chevron_up.svg",
212 IconName::ExpandVertical => "icons/expand_vertical.svg",
213 IconName::Close => "icons/x.svg",
214 IconName::Code => "icons/code.svg",
215 IconName::Collab => "icons/user_group_16.svg",
216 IconName::Command => "icons/command.svg",
217 IconName::Control => "icons/control.svg",
218 IconName::Copilot => "icons/copilot.svg",
219 IconName::CopilotDisabled => "icons/copilot_disabled.svg",
220 IconName::CopilotError => "icons/copilot_error.svg",
221 IconName::CopilotInit => "icons/copilot_init.svg",
222 IconName::Copy => "icons/copy.svg",
223 IconName::Dash => "icons/dash.svg",
224 IconName::Delete => "icons/delete.svg",
225 IconName::Disconnected => "icons/disconnected.svg",
226 IconName::Ellipsis => "icons/ellipsis.svg",
227 IconName::Envelope => "icons/feedback.svg",
228 IconName::Escape => "icons/escape.svg",
229 IconName::ExclamationTriangle => "icons/warning.svg",
230 IconName::Exit => "icons/exit.svg",
231 IconName::ExternalLink => "icons/external_link.svg",
232 IconName::File => "icons/file.svg",
233 IconName::FileDoc => "icons/file_icons/book.svg",
234 IconName::FileGeneric => "icons/file_icons/file.svg",
235 IconName::FileGit => "icons/file_icons/git.svg",
236 IconName::FileLock => "icons/file_icons/lock.svg",
237 IconName::FileRust => "icons/file_icons/rust.svg",
238 IconName::FileToml => "icons/file_icons/toml.svg",
239 IconName::FileTree => "icons/project.svg",
240 IconName::Filter => "icons/filter.svg",
241 IconName::Folder => "icons/file_icons/folder.svg",
242 IconName::FolderOpen => "icons/file_icons/folder_open.svg",
243 IconName::FolderX => "icons/stop_sharing.svg",
244 IconName::Github => "icons/github.svg",
245 IconName::Hash => "icons/hash.svg",
246 IconName::Indicator => "icons/indicator.svg",
247 IconName::IndicatorX => "icons/indicator_x.svg",
248 IconName::InlayHint => "icons/inlay_hint.svg",
249 IconName::Link => "icons/link.svg",
250 IconName::MagicWand => "icons/magic_wand.svg",
251 IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
252 IconName::MailOpen => "icons/mail_open.svg",
253 IconName::Maximize => "icons/maximize.svg",
254 IconName::Menu => "icons/menu.svg",
255 IconName::MessageBubbles => "icons/conversations.svg",
256 IconName::Mic => "icons/mic.svg",
257 IconName::MicMute => "icons/mic_mute.svg",
258 IconName::Minimize => "icons/minimize.svg",
259 IconName::Option => "icons/option.svg",
260 IconName::PageDown => "icons/page_down.svg",
261 IconName::PageUp => "icons/page_up.svg",
262 IconName::Person => "icons/person.svg",
263 IconName::Pencil => "icons/pencil.svg",
264 IconName::Play => "icons/play.svg",
265 IconName::Plus => "icons/plus.svg",
266 IconName::Public => "icons/public.svg",
267 IconName::Quote => "icons/quote.svg",
268 IconName::Regex => "icons/regex.svg",
269 IconName::Replace => "icons/replace.svg",
270 IconName::ReplaceAll => "icons/replace_all.svg",
271 IconName::ReplaceNext => "icons/replace_next.svg",
272 IconName::Return => "icons/return.svg",
273 IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
274 IconName::Settings => "icons/file_icons/settings.svg",
275 IconName::Sliders => "icons/sliders.svg",
276 IconName::Screen => "icons/desktop.svg",
277 IconName::SelectAll => "icons/select_all.svg",
278 IconName::Server => "icons/server.svg",
279 IconName::Shift => "icons/shift.svg",
280 IconName::Snip => "icons/snip.svg",
281 IconName::Space => "icons/space.svg",
282 IconName::Split => "icons/split.svg",
283 IconName::Spinner => "icons/spinner.svg",
284 IconName::Supermaven => "icons/supermaven.svg",
285 IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
286 IconName::SupermavenError => "icons/supermaven_error.svg",
287 IconName::SupermavenInit => "icons/supermaven_init.svg",
288 IconName::Strikethrough => "icons/strikethrough.svg",
289 IconName::Tab => "icons/tab.svg",
290 IconName::Terminal => "icons/terminal.svg",
291 IconName::Trash => "icons/trash.svg",
292 IconName::Update => "icons/update.svg",
293 IconName::WholeWord => "icons/word_search.svg",
294 IconName::XCircle => "icons/error.svg",
295 IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
296 IconName::ZedAssistant => "icons/zed_assistant.svg",
297 IconName::PullRequest => "icons/pull_request.svg",
298 }
299 }
300}
301
302#[derive(IntoElement)]
303pub struct Icon {
304 path: SharedString,
305 color: Color,
306 size: Rems,
307 transformation: Transformation,
308}
309
310impl Icon {
311 pub fn new(icon: IconName) -> Self {
312 Self {
313 path: icon.path().into(),
314 color: Color::default(),
315 size: IconSize::default().rems(),
316 transformation: Transformation::default(),
317 }
318 }
319
320 pub fn from_path(path: impl Into<SharedString>) -> Self {
321 Self {
322 path: path.into(),
323 color: Color::default(),
324 size: IconSize::default().rems(),
325 transformation: Transformation::default(),
326 }
327 }
328
329 pub fn color(mut self, color: Color) -> Self {
330 self.color = color;
331 self
332 }
333
334 pub fn size(mut self, size: IconSize) -> Self {
335 self.size = size.rems();
336 self
337 }
338
339 /// Sets a custom size for the icon, in [`Rems`].
340 ///
341 /// Not to be exposed outside of the `ui` crate.
342 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
343 self.size = size;
344 self
345 }
346
347 pub fn transform(mut self, transformation: Transformation) -> Self {
348 self.transformation = transformation;
349 self
350 }
351}
352
353impl RenderOnce for Icon {
354 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
355 svg()
356 .with_transformation(self.transformation)
357 .size(self.size)
358 .flex_none()
359 .path(self.path)
360 .text_color(self.color.color(cx))
361 }
362}
363
364#[derive(IntoElement)]
365pub struct DecoratedIcon {
366 icon: Icon,
367 decoration: IconDecoration,
368 decoration_color: Color,
369 parent_background: Option<Hsla>,
370}
371
372impl DecoratedIcon {
373 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
374 Self {
375 icon,
376 decoration,
377 decoration_color: Color::Default,
378 parent_background: None,
379 }
380 }
381
382 pub fn decoration_color(mut self, color: Color) -> Self {
383 self.decoration_color = color;
384 self
385 }
386
387 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
388 self.parent_background = background;
389 self
390 }
391}
392
393impl RenderOnce for DecoratedIcon {
394 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
395 let background = self
396 .parent_background
397 .unwrap_or(cx.theme().colors().background);
398
399 let size = self.icon.size;
400
401 let decoration_icon = match self.decoration {
402 IconDecoration::Strikethrough => IconName::Strikethrough,
403 IconDecoration::IndicatorDot => IconName::Indicator,
404 IconDecoration::X => IconName::IndicatorX,
405 };
406
407 let decoration_svg = |icon: IconName| {
408 svg()
409 .absolute()
410 .top_0()
411 .left_0()
412 .path(icon.path())
413 .size(size)
414 .flex_none()
415 .text_color(self.decoration_color.color(cx))
416 };
417
418 let decoration_knockout = |icon: IconName| {
419 svg()
420 .absolute()
421 .top(-rems_from_px(2.))
422 .left(-rems_from_px(3.))
423 .path(icon.path())
424 .size(size + rems_from_px(2.))
425 .flex_none()
426 .text_color(background)
427 };
428
429 div()
430 .relative()
431 .size(self.icon.size)
432 .child(self.icon)
433 .child(decoration_knockout(decoration_icon))
434 .child(decoration_svg(decoration_icon))
435 }
436}
437
438#[derive(IntoElement)]
439pub struct IconWithIndicator {
440 icon: Icon,
441 indicator: Option<Indicator>,
442 indicator_border_color: Option<Hsla>,
443}
444
445impl IconWithIndicator {
446 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
447 Self {
448 icon,
449 indicator,
450 indicator_border_color: None,
451 }
452 }
453
454 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
455 self.indicator = indicator;
456 self
457 }
458
459 pub fn indicator_color(mut self, color: Color) -> Self {
460 if let Some(indicator) = self.indicator.as_mut() {
461 indicator.color = color;
462 }
463 self
464 }
465
466 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
467 self.indicator_border_color = color;
468 self
469 }
470}
471
472impl RenderOnce for IconWithIndicator {
473 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
474 let indicator_border_color = self
475 .indicator_border_color
476 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
477
478 div()
479 .relative()
480 .child(self.icon)
481 .when_some(self.indicator, |this, indicator| {
482 this.child(
483 div()
484 .absolute()
485 .w_2()
486 .h_2()
487 .border_1()
488 .border_color(indicator_border_color)
489 .rounded_full()
490 .bottom_neg_0p5()
491 .right_neg_1()
492 .child(indicator),
493 )
494 })
495 }
496}