1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
2use serde::{Deserialize, Serialize};
3use strum::{EnumIter, EnumString, IntoStaticStr};
4
5use crate::{prelude::*, Indicator};
6
7#[derive(IntoElement)]
8pub enum AnyIcon {
9 Icon(Icon),
10 AnimatedIcon(AnimationElement<Icon>),
11}
12
13impl AnyIcon {
14 /// Returns a new [`AnyIcon`] after applying the given mapping function
15 /// to the contained [`Icon`].
16 pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
17 match self {
18 Self::Icon(icon) => Self::Icon(f(icon)),
19 Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
20 }
21 }
22}
23
24impl From<Icon> for AnyIcon {
25 fn from(value: Icon) -> Self {
26 Self::Icon(value)
27 }
28}
29
30impl From<AnimationElement<Icon>> for AnyIcon {
31 fn from(value: AnimationElement<Icon>) -> Self {
32 Self::AnimatedIcon(value)
33 }
34}
35
36impl RenderOnce for AnyIcon {
37 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
38 match self {
39 Self::Icon(icon) => icon.into_any_element(),
40 Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
41 }
42 }
43}
44
45/// The decoration for an icon.
46///
47/// For example, this can show an indicator, an "x",
48/// or a diagonal strikethrough to indicate something is disabled.
49#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
50pub enum IconDecoration {
51 Strikethrough,
52 IndicatorDot,
53 X,
54}
55
56#[derive(Default, PartialEq, Copy, Clone)]
57pub enum IconSize {
58 /// 10px
59 Indicator,
60 /// 12px
61 XSmall,
62 /// 14px
63 Small,
64 #[default]
65 /// 16px
66 Medium,
67}
68
69impl IconSize {
70 pub fn rems(self) -> Rems {
71 match self {
72 IconSize::Indicator => rems_from_px(10.),
73 IconSize::XSmall => rems_from_px(12.),
74 IconSize::Small => rems_from_px(14.),
75 IconSize::Medium => rems_from_px(16.),
76 }
77 }
78
79 /// Returns the individual components of the square that contains this [`IconSize`].
80 ///
81 /// The returned tuple contains:
82 /// 1. The length of one side of the square
83 /// 2. The padding of one side of the square
84 pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
85 let icon_size = self.rems() * cx.rem_size();
86 let padding = match self {
87 IconSize::Indicator => Spacing::None.px(cx),
88 IconSize::XSmall => Spacing::XSmall.px(cx),
89 IconSize::Small => Spacing::XSmall.px(cx),
90 IconSize::Medium => Spacing::XSmall.px(cx),
91 };
92
93 (icon_size, padding)
94 }
95
96 /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
97 pub fn square(&self, cx: &mut WindowContext) -> Pixels {
98 let (icon_size, padding) = self.square_components(cx);
99
100 icon_size + padding * 2.
101 }
102}
103
104#[derive(
105 Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
106)]
107pub enum IconName {
108 Ai,
109 AiAnthropic,
110 AiAnthropicHosted,
111 AiOpenAi,
112 AiGoogle,
113 AiOllama,
114 AiZed,
115 ArrowCircle,
116 ArrowDown,
117 ArrowDownFromLine,
118 ArrowLeft,
119 ArrowRight,
120 ArrowUp,
121 ArrowUpFromLine,
122 ArrowUpRight,
123 AtSign,
124 AudioOff,
125 AudioOn,
126 Backspace,
127 Bell,
128 BellDot,
129 BellOff,
130 BellRing,
131 Bolt,
132 Book,
133 BookCopy,
134 BookPlus,
135 CaseSensitive,
136 Check,
137 ChevronDown,
138 /// This chevron indicates a popover menu.
139 ChevronDownSmall,
140 ChevronLeft,
141 ChevronRight,
142 ChevronUp,
143 ChevronUpDown,
144 Close,
145 Code,
146 Collab,
147 Command,
148 Context,
149 Control,
150 Copilot,
151 CopilotDisabled,
152 CopilotError,
153 CopilotInit,
154 Copy,
155 CountdownTimer,
156 Dash,
157 DatabaseZap,
158 Delete,
159 Disconnected,
160 Download,
161 Ellipsis,
162 EllipsisVertical,
163 Envelope,
164 Escape,
165 ExclamationTriangle,
166 Exit,
167 ExpandVertical,
168 ExternalLink,
169 Eye,
170 File,
171 FileDoc,
172 FileGeneric,
173 FileGit,
174 FileLock,
175 FileRust,
176 FileToml,
177 FileTree,
178 FileText,
179 FileCode,
180 Filter,
181 Folder,
182 FolderOpen,
183 FolderX,
184 Font,
185 FontSize,
186 FontWeight,
187 Github,
188 GenericMinimize,
189 GenericMaximize,
190 GenericClose,
191 GenericRestore,
192 Hash,
193 HistoryRerun,
194 Indicator,
195 IndicatorX,
196 InlayHint,
197 Library,
198 LineHeight,
199 Link,
200 ListTree,
201 MagnifyingGlass,
202 MailOpen,
203 Maximize,
204 Menu,
205 MessageBubbles,
206 Mic,
207 MicMute,
208 Microscope,
209 Minimize,
210 Option,
211 PageDown,
212 PageUp,
213 Pencil,
214 Person,
215 Pin,
216 Play,
217 Plus,
218 PocketKnife,
219 Public,
220 PullRequest,
221 Quote,
222 Regex,
223 ReplNeutral,
224 Replace,
225 ReplaceAll,
226 ReplaceNext,
227 ReplyArrowRight,
228 Rerun,
229 Return,
230 Reveal,
231 Route,
232 RotateCcw,
233 RotateCw,
234 Save,
235 Screen,
236 SearchSelection,
237 SearchCode,
238 SelectAll,
239 Server,
240 Settings,
241 Shift,
242 Slash,
243 SlashSquare,
244 Sliders,
245 SlidersAlt,
246 Snip,
247 Space,
248 Sparkle,
249 SparkleAlt,
250 SparkleFilled,
251 Spinner,
252 Split,
253 Star,
254 StarFilled,
255 Stop,
256 Strikethrough,
257 Supermaven,
258 SupermavenDisabled,
259 SupermavenError,
260 SupermavenInit,
261 Tab,
262 Terminal,
263 TextCursor,
264 TextSelect,
265 Trash,
266 TriangleRight,
267 Undo,
268 Unpin,
269 Update,
270 WholeWord,
271 XCircle,
272 ZedAssistant,
273 ZedAssistantFilled,
274 ZedXCopilot,
275 Visible,
276}
277
278impl IconName {
279 pub fn path(self) -> &'static str {
280 match self {
281 IconName::Ai => "icons/ai.svg",
282 IconName::AiAnthropic => "icons/ai_anthropic.svg",
283 IconName::AiAnthropicHosted => "icons/ai_anthropic_hosted.svg",
284 IconName::AiOpenAi => "icons/ai_open_ai.svg",
285 IconName::AiGoogle => "icons/ai_google.svg",
286 IconName::AiOllama => "icons/ai_ollama.svg",
287 IconName::AiZed => "icons/ai_zed.svg",
288 IconName::ArrowCircle => "icons/arrow_circle.svg",
289 IconName::ArrowDown => "icons/arrow_down.svg",
290 IconName::ArrowDownFromLine => "icons/arrow_down_from_line.svg",
291 IconName::ArrowLeft => "icons/arrow_left.svg",
292 IconName::ArrowRight => "icons/arrow_right.svg",
293 IconName::ArrowUp => "icons/arrow_up.svg",
294 IconName::ArrowUpFromLine => "icons/arrow_up_from_line.svg",
295 IconName::ArrowUpRight => "icons/arrow_up_right.svg",
296 IconName::AtSign => "icons/at_sign.svg",
297 IconName::AudioOff => "icons/speaker_off.svg",
298 IconName::AudioOn => "icons/speaker_loud.svg",
299 IconName::Backspace => "icons/backspace.svg",
300 IconName::Bell => "icons/bell.svg",
301 IconName::BellDot => "icons/bell_dot.svg",
302 IconName::BellOff => "icons/bell_off.svg",
303 IconName::BellRing => "icons/bell_ring.svg",
304 IconName::Bolt => "icons/bolt.svg",
305 IconName::Book => "icons/book.svg",
306 IconName::BookCopy => "icons/book_copy.svg",
307 IconName::BookPlus => "icons/book_plus.svg",
308 IconName::CaseSensitive => "icons/case_insensitive.svg",
309 IconName::Check => "icons/check.svg",
310 IconName::ChevronDown => "icons/chevron_down.svg",
311 IconName::ChevronDownSmall => "icons/chevron_down_small.svg",
312 IconName::ChevronLeft => "icons/chevron_left.svg",
313 IconName::ChevronRight => "icons/chevron_right.svg",
314 IconName::ChevronUp => "icons/chevron_up.svg",
315 IconName::ChevronUpDown => "icons/chevron_up_down.svg",
316 IconName::Close => "icons/x.svg",
317 IconName::Code => "icons/code.svg",
318 IconName::Collab => "icons/user_group_16.svg",
319 IconName::Command => "icons/command.svg",
320 IconName::Context => "icons/context.svg",
321 IconName::Control => "icons/control.svg",
322 IconName::Copilot => "icons/copilot.svg",
323 IconName::CopilotDisabled => "icons/copilot_disabled.svg",
324 IconName::CopilotError => "icons/copilot_error.svg",
325 IconName::CopilotInit => "icons/copilot_init.svg",
326 IconName::Copy => "icons/copy.svg",
327 IconName::CountdownTimer => "icons/countdown_timer.svg",
328 IconName::Dash => "icons/dash.svg",
329 IconName::DatabaseZap => "icons/database_zap.svg",
330 IconName::Delete => "icons/delete.svg",
331 IconName::Disconnected => "icons/disconnected.svg",
332 IconName::Download => "icons/download.svg",
333 IconName::Ellipsis => "icons/ellipsis.svg",
334 IconName::EllipsisVertical => "icons/ellipsis_vertical.svg",
335 IconName::Envelope => "icons/feedback.svg",
336 IconName::Escape => "icons/escape.svg",
337 IconName::ExclamationTriangle => "icons/warning.svg",
338 IconName::Exit => "icons/exit.svg",
339 IconName::ExpandVertical => "icons/expand_vertical.svg",
340 IconName::ExternalLink => "icons/external_link.svg",
341 IconName::Eye => "icons/eye.svg",
342 IconName::File => "icons/file.svg",
343 IconName::FileDoc => "icons/file_icons/book.svg",
344 IconName::FileGeneric => "icons/file_icons/file.svg",
345 IconName::FileGit => "icons/file_icons/git.svg",
346 IconName::FileLock => "icons/file_icons/lock.svg",
347 IconName::FileRust => "icons/file_icons/rust.svg",
348 IconName::FileToml => "icons/file_icons/toml.svg",
349 IconName::FileTree => "icons/project.svg",
350 IconName::FileCode => "icons/file_code.svg",
351 IconName::FileText => "icons/file_text.svg",
352 IconName::Filter => "icons/filter.svg",
353 IconName::Folder => "icons/file_icons/folder.svg",
354 IconName::FolderOpen => "icons/file_icons/folder_open.svg",
355 IconName::FolderX => "icons/stop_sharing.svg",
356 IconName::Font => "icons/font.svg",
357 IconName::FontSize => "icons/font_size.svg",
358 IconName::FontWeight => "icons/font_weight.svg",
359 IconName::Github => "icons/github.svg",
360 IconName::GenericMinimize => "icons/generic_minimize.svg",
361 IconName::GenericMaximize => "icons/generic_maximize.svg",
362 IconName::GenericClose => "icons/generic_close.svg",
363 IconName::GenericRestore => "icons/generic_restore.svg",
364 IconName::Hash => "icons/hash.svg",
365 IconName::HistoryRerun => "icons/history_rerun.svg",
366 IconName::Indicator => "icons/indicator.svg",
367 IconName::IndicatorX => "icons/indicator_x.svg",
368 IconName::InlayHint => "icons/inlay_hint.svg",
369 IconName::Library => "icons/library.svg",
370 IconName::LineHeight => "icons/line_height.svg",
371 IconName::Link => "icons/link.svg",
372 IconName::ListTree => "icons/list_tree.svg",
373 IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
374 IconName::MailOpen => "icons/mail_open.svg",
375 IconName::Maximize => "icons/maximize.svg",
376 IconName::Menu => "icons/menu.svg",
377 IconName::MessageBubbles => "icons/conversations.svg",
378 IconName::Mic => "icons/mic.svg",
379 IconName::MicMute => "icons/mic_mute.svg",
380 IconName::Microscope => "icons/microscope.svg",
381 IconName::Minimize => "icons/minimize.svg",
382 IconName::Option => "icons/option.svg",
383 IconName::PageDown => "icons/page_down.svg",
384 IconName::PageUp => "icons/page_up.svg",
385 IconName::Pencil => "icons/pencil.svg",
386 IconName::Person => "icons/person.svg",
387 IconName::Pin => "icons/pin.svg",
388 IconName::Play => "icons/play.svg",
389 IconName::Plus => "icons/plus.svg",
390 IconName::PocketKnife => "icons/pocket_knife.svg",
391 IconName::Public => "icons/public.svg",
392 IconName::PullRequest => "icons/pull_request.svg",
393 IconName::Quote => "icons/quote.svg",
394 IconName::Regex => "icons/regex.svg",
395 IconName::ReplNeutral => "icons/repl_neutral.svg",
396 IconName::Replace => "icons/replace.svg",
397 IconName::ReplaceAll => "icons/replace_all.svg",
398 IconName::ReplaceNext => "icons/replace_next.svg",
399 IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
400 IconName::Rerun => "icons/rerun.svg",
401 IconName::Return => "icons/return.svg",
402 IconName::Reveal => "icons/reveal.svg",
403 IconName::RotateCcw => "icons/rotate_ccw.svg",
404 IconName::RotateCw => "icons/rotate_cw.svg",
405 IconName::Route => "icons/route.svg",
406 IconName::Save => "icons/save.svg",
407 IconName::Screen => "icons/desktop.svg",
408 IconName::SearchSelection => "icons/search_selection.svg",
409 IconName::SearchCode => "icons/search_code.svg",
410 IconName::SelectAll => "icons/select_all.svg",
411 IconName::Server => "icons/server.svg",
412 IconName::Settings => "icons/file_icons/settings.svg",
413 IconName::Shift => "icons/shift.svg",
414 IconName::Slash => "icons/slash.svg",
415 IconName::SlashSquare => "icons/slash_square.svg",
416 IconName::Sliders => "icons/sliders.svg",
417 IconName::SlidersAlt => "icons/sliders-alt.svg",
418 IconName::Snip => "icons/snip.svg",
419 IconName::Space => "icons/space.svg",
420 IconName::Sparkle => "icons/sparkle.svg",
421 IconName::SparkleAlt => "icons/sparkle_alt.svg",
422 IconName::SparkleFilled => "icons/sparkle_filled.svg",
423 IconName::Spinner => "icons/spinner.svg",
424 IconName::Split => "icons/split.svg",
425 IconName::Star => "icons/star.svg",
426 IconName::StarFilled => "icons/star_filled.svg",
427 IconName::Stop => "icons/stop.svg",
428 IconName::Strikethrough => "icons/strikethrough.svg",
429 IconName::Supermaven => "icons/supermaven.svg",
430 IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
431 IconName::SupermavenError => "icons/supermaven_error.svg",
432 IconName::SupermavenInit => "icons/supermaven_init.svg",
433 IconName::Tab => "icons/tab.svg",
434 IconName::Terminal => "icons/terminal.svg",
435 IconName::TextCursor => "icons/text-cursor.svg",
436 IconName::TextSelect => "icons/text_select.svg",
437 IconName::Trash => "icons/trash.svg",
438 IconName::TriangleRight => "icons/triangle_right.svg",
439 IconName::Unpin => "icons/unpin.svg",
440 IconName::Update => "icons/update.svg",
441 IconName::Undo => "icons/undo.svg",
442 IconName::WholeWord => "icons/word_search.svg",
443 IconName::XCircle => "icons/error.svg",
444 IconName::ZedAssistant => "icons/zed_assistant.svg",
445 IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
446 IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
447 IconName::Visible => "icons/visible.svg",
448 }
449 }
450}
451
452#[derive(IntoElement)]
453pub struct Icon {
454 path: SharedString,
455 color: Color,
456 size: Rems,
457 transformation: Transformation,
458}
459
460impl Icon {
461 pub fn new(icon: IconName) -> Self {
462 Self {
463 path: icon.path().into(),
464 color: Color::default(),
465 size: IconSize::default().rems(),
466 transformation: Transformation::default(),
467 }
468 }
469
470 pub fn from_path(path: impl Into<SharedString>) -> Self {
471 Self {
472 path: path.into(),
473 color: Color::default(),
474 size: IconSize::default().rems(),
475 transformation: Transformation::default(),
476 }
477 }
478
479 pub fn color(mut self, color: Color) -> Self {
480 self.color = color;
481 self
482 }
483
484 pub fn size(mut self, size: IconSize) -> Self {
485 self.size = size.rems();
486 self
487 }
488
489 /// Sets a custom size for the icon, in [`Rems`].
490 ///
491 /// Not to be exposed outside of the `ui` crate.
492 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
493 self.size = size;
494 self
495 }
496
497 pub fn transform(mut self, transformation: Transformation) -> Self {
498 self.transformation = transformation;
499 self
500 }
501}
502
503impl RenderOnce for Icon {
504 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
505 svg()
506 .with_transformation(self.transformation)
507 .size(self.size)
508 .flex_none()
509 .path(self.path)
510 .text_color(self.color.color(cx))
511 }
512}
513
514#[derive(IntoElement)]
515pub struct DecoratedIcon {
516 icon: Icon,
517 decoration: IconDecoration,
518 decoration_color: Color,
519 parent_background: Option<Hsla>,
520}
521
522impl DecoratedIcon {
523 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
524 Self {
525 icon,
526 decoration,
527 decoration_color: Color::Default,
528 parent_background: None,
529 }
530 }
531
532 pub fn decoration_color(mut self, color: Color) -> Self {
533 self.decoration_color = color;
534 self
535 }
536
537 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
538 self.parent_background = background;
539 self
540 }
541}
542
543impl RenderOnce for DecoratedIcon {
544 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
545 let background = self
546 .parent_background
547 .unwrap_or(cx.theme().colors().background);
548
549 let size = self.icon.size;
550
551 let decoration_icon = match self.decoration {
552 IconDecoration::Strikethrough => IconName::Strikethrough,
553 IconDecoration::IndicatorDot => IconName::Indicator,
554 IconDecoration::X => IconName::IndicatorX,
555 };
556
557 let decoration_svg = |icon: IconName| {
558 svg()
559 .absolute()
560 .top_0()
561 .left_0()
562 .path(icon.path())
563 .size(size)
564 .flex_none()
565 .text_color(self.decoration_color.color(cx))
566 };
567
568 let decoration_knockout = |icon: IconName| {
569 svg()
570 .absolute()
571 .top(-rems_from_px(2.))
572 .left(-rems_from_px(3.))
573 .path(icon.path())
574 .size(size + rems_from_px(2.))
575 .flex_none()
576 .text_color(background)
577 };
578
579 div()
580 .relative()
581 .size(self.icon.size)
582 .child(self.icon)
583 .child(decoration_knockout(decoration_icon))
584 .child(decoration_svg(decoration_icon))
585 }
586}
587
588#[derive(IntoElement)]
589pub struct IconWithIndicator {
590 icon: Icon,
591 indicator: Option<Indicator>,
592 indicator_border_color: Option<Hsla>,
593}
594
595impl IconWithIndicator {
596 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
597 Self {
598 icon,
599 indicator,
600 indicator_border_color: None,
601 }
602 }
603
604 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
605 self.indicator = indicator;
606 self
607 }
608
609 pub fn indicator_color(mut self, color: Color) -> Self {
610 if let Some(indicator) = self.indicator.as_mut() {
611 indicator.color = color;
612 }
613 self
614 }
615
616 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
617 self.indicator_border_color = color;
618 self
619 }
620}
621
622impl RenderOnce for IconWithIndicator {
623 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
624 let indicator_border_color = self
625 .indicator_border_color
626 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
627
628 div()
629 .relative()
630 .child(self.icon)
631 .when_some(self.indicator, |this, indicator| {
632 this.child(
633 div()
634 .absolute()
635 .w_2()
636 .h_2()
637 .border_1()
638 .border_color(indicator_border_color)
639 .rounded_full()
640 .bottom_neg_0p5()
641 .right_neg_1()
642 .child(indicator),
643 )
644 })
645 }
646}