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 strkethrough 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 Public,
219 PullRequest,
220 Quote,
221 Regex,
222 ReplNeutral,
223 Replace,
224 ReplaceAll,
225 ReplaceNext,
226 ReplyArrowRight,
227 Rerun,
228 Return,
229 Reveal,
230 Route,
231 RotateCcw,
232 RotateCw,
233 Save,
234 Screen,
235 SearchSelection,
236 SearchCode,
237 SelectAll,
238 Server,
239 Settings,
240 Shift,
241 Slash,
242 SlashSquare,
243 Sliders,
244 SlidersAlt,
245 Snip,
246 Space,
247 Sparkle,
248 SparkleAlt,
249 SparkleFilled,
250 Spinner,
251 Split,
252 Star,
253 StarFilled,
254 Stop,
255 Strikethrough,
256 Supermaven,
257 SupermavenDisabled,
258 SupermavenError,
259 SupermavenInit,
260 Tab,
261 Terminal,
262 TextCursor,
263 TextSelect,
264 Trash,
265 TriangleRight,
266 Undo,
267 Unpin,
268 Update,
269 WholeWord,
270 XCircle,
271 ZedAssistant,
272 ZedAssistantFilled,
273 ZedXCopilot,
274 Visible,
275}
276
277impl IconName {
278 pub fn path(self) -> &'static str {
279 match self {
280 IconName::Ai => "icons/ai.svg",
281 IconName::AiAnthropic => "icons/ai_anthropic.svg",
282 IconName::AiAnthropicHosted => "icons/ai_anthropic_hosted.svg",
283 IconName::AiOpenAi => "icons/ai_open_ai.svg",
284 IconName::AiGoogle => "icons/ai_google.svg",
285 IconName::AiOllama => "icons/ai_ollama.svg",
286 IconName::AiZed => "icons/ai_zed.svg",
287 IconName::ArrowCircle => "icons/arrow_circle.svg",
288 IconName::ArrowDown => "icons/arrow_down.svg",
289 IconName::ArrowDownFromLine => "icons/arrow_down_from_line.svg",
290 IconName::ArrowLeft => "icons/arrow_left.svg",
291 IconName::ArrowRight => "icons/arrow_right.svg",
292 IconName::ArrowUp => "icons/arrow_up.svg",
293 IconName::ArrowUpFromLine => "icons/arrow_up_from_line.svg",
294 IconName::ArrowUpRight => "icons/arrow_up_right.svg",
295 IconName::AtSign => "icons/at_sign.svg",
296 IconName::AudioOff => "icons/speaker_off.svg",
297 IconName::AudioOn => "icons/speaker_loud.svg",
298 IconName::Backspace => "icons/backspace.svg",
299 IconName::Bell => "icons/bell.svg",
300 IconName::BellDot => "icons/bell_dot.svg",
301 IconName::BellOff => "icons/bell_off.svg",
302 IconName::BellRing => "icons/bell_ring.svg",
303 IconName::Bolt => "icons/bolt.svg",
304 IconName::Book => "icons/book.svg",
305 IconName::BookCopy => "icons/book_copy.svg",
306 IconName::BookPlus => "icons/book_plus.svg",
307 IconName::CaseSensitive => "icons/case_insensitive.svg",
308 IconName::Check => "icons/check.svg",
309 IconName::ChevronDown => "icons/chevron_down.svg",
310 IconName::ChevronDownSmall => "icons/chevron_down_small.svg",
311 IconName::ChevronLeft => "icons/chevron_left.svg",
312 IconName::ChevronRight => "icons/chevron_right.svg",
313 IconName::ChevronUp => "icons/chevron_up.svg",
314 IconName::ChevronUpDown => "icons/chevron_up_down.svg",
315 IconName::Close => "icons/x.svg",
316 IconName::Code => "icons/code.svg",
317 IconName::Collab => "icons/user_group_16.svg",
318 IconName::Command => "icons/command.svg",
319 IconName::Context => "icons/context.svg",
320 IconName::Control => "icons/control.svg",
321 IconName::Copilot => "icons/copilot.svg",
322 IconName::CopilotDisabled => "icons/copilot_disabled.svg",
323 IconName::CopilotError => "icons/copilot_error.svg",
324 IconName::CopilotInit => "icons/copilot_init.svg",
325 IconName::Copy => "icons/copy.svg",
326 IconName::CountdownTimer => "icons/countdown_timer.svg",
327 IconName::Dash => "icons/dash.svg",
328 IconName::DatabaseZap => "icons/database_zap.svg",
329 IconName::Delete => "icons/delete.svg",
330 IconName::Disconnected => "icons/disconnected.svg",
331 IconName::Download => "icons/download.svg",
332 IconName::Ellipsis => "icons/ellipsis.svg",
333 IconName::EllipsisVertical => "icons/ellipsis_vertical.svg",
334 IconName::Envelope => "icons/feedback.svg",
335 IconName::Escape => "icons/escape.svg",
336 IconName::ExclamationTriangle => "icons/warning.svg",
337 IconName::Exit => "icons/exit.svg",
338 IconName::ExpandVertical => "icons/expand_vertical.svg",
339 IconName::ExternalLink => "icons/external_link.svg",
340 IconName::Eye => "icons/eye.svg",
341 IconName::File => "icons/file.svg",
342 IconName::FileDoc => "icons/file_icons/book.svg",
343 IconName::FileGeneric => "icons/file_icons/file.svg",
344 IconName::FileGit => "icons/file_icons/git.svg",
345 IconName::FileLock => "icons/file_icons/lock.svg",
346 IconName::FileRust => "icons/file_icons/rust.svg",
347 IconName::FileToml => "icons/file_icons/toml.svg",
348 IconName::FileTree => "icons/project.svg",
349 IconName::FileCode => "icons/file_code.svg",
350 IconName::FileText => "icons/file_text.svg",
351 IconName::Filter => "icons/filter.svg",
352 IconName::Folder => "icons/file_icons/folder.svg",
353 IconName::FolderOpen => "icons/file_icons/folder_open.svg",
354 IconName::FolderX => "icons/stop_sharing.svg",
355 IconName::Font => "icons/font.svg",
356 IconName::FontSize => "icons/font_size.svg",
357 IconName::FontWeight => "icons/font_weight.svg",
358 IconName::Github => "icons/github.svg",
359 IconName::GenericMinimize => "icons/generic_minimize.svg",
360 IconName::GenericMaximize => "icons/generic_maximize.svg",
361 IconName::GenericClose => "icons/generic_close.svg",
362 IconName::GenericRestore => "icons/generic_restore.svg",
363 IconName::Hash => "icons/hash.svg",
364 IconName::HistoryRerun => "icons/history_rerun.svg",
365 IconName::Indicator => "icons/indicator.svg",
366 IconName::IndicatorX => "icons/indicator_x.svg",
367 IconName::InlayHint => "icons/inlay_hint.svg",
368 IconName::Library => "icons/library.svg",
369 IconName::LineHeight => "icons/line_height.svg",
370 IconName::Link => "icons/link.svg",
371 IconName::ListTree => "icons/list_tree.svg",
372 IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
373 IconName::MailOpen => "icons/mail_open.svg",
374 IconName::Maximize => "icons/maximize.svg",
375 IconName::Menu => "icons/menu.svg",
376 IconName::MessageBubbles => "icons/conversations.svg",
377 IconName::Mic => "icons/mic.svg",
378 IconName::MicMute => "icons/mic_mute.svg",
379 IconName::Microscope => "icons/microscope.svg",
380 IconName::Minimize => "icons/minimize.svg",
381 IconName::Option => "icons/option.svg",
382 IconName::PageDown => "icons/page_down.svg",
383 IconName::PageUp => "icons/page_up.svg",
384 IconName::Pencil => "icons/pencil.svg",
385 IconName::Person => "icons/person.svg",
386 IconName::Pin => "icons/pin.svg",
387 IconName::Play => "icons/play.svg",
388 IconName::Plus => "icons/plus.svg",
389 IconName::Public => "icons/public.svg",
390 IconName::PullRequest => "icons/pull_request.svg",
391 IconName::Quote => "icons/quote.svg",
392 IconName::Regex => "icons/regex.svg",
393 IconName::ReplNeutral => "icons/repl_neutral.svg",
394 IconName::Replace => "icons/replace.svg",
395 IconName::ReplaceAll => "icons/replace_all.svg",
396 IconName::ReplaceNext => "icons/replace_next.svg",
397 IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
398 IconName::Rerun => "icons/rerun.svg",
399 IconName::Return => "icons/return.svg",
400 IconName::Reveal => "icons/reveal.svg",
401 IconName::RotateCcw => "icons/rotate_ccw.svg",
402 IconName::RotateCw => "icons/rotate_cw.svg",
403 IconName::Route => "icons/route.svg",
404 IconName::Save => "icons/save.svg",
405 IconName::Screen => "icons/desktop.svg",
406 IconName::SearchSelection => "icons/search_selection.svg",
407 IconName::SearchCode => "icons/search_code.svg",
408 IconName::SelectAll => "icons/select_all.svg",
409 IconName::Server => "icons/server.svg",
410 IconName::Settings => "icons/file_icons/settings.svg",
411 IconName::Shift => "icons/shift.svg",
412 IconName::Slash => "icons/slash.svg",
413 IconName::SlashSquare => "icons/slash_square.svg",
414 IconName::Sliders => "icons/sliders.svg",
415 IconName::SlidersAlt => "icons/sliders-alt.svg",
416 IconName::Snip => "icons/snip.svg",
417 IconName::Space => "icons/space.svg",
418 IconName::Sparkle => "icons/sparkle.svg",
419 IconName::SparkleAlt => "icons/sparkle_alt.svg",
420 IconName::SparkleFilled => "icons/sparkle_filled.svg",
421 IconName::Spinner => "icons/spinner.svg",
422 IconName::Split => "icons/split.svg",
423 IconName::Star => "icons/star.svg",
424 IconName::StarFilled => "icons/star_filled.svg",
425 IconName::Stop => "icons/stop.svg",
426 IconName::Strikethrough => "icons/strikethrough.svg",
427 IconName::Supermaven => "icons/supermaven.svg",
428 IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
429 IconName::SupermavenError => "icons/supermaven_error.svg",
430 IconName::SupermavenInit => "icons/supermaven_init.svg",
431 IconName::Tab => "icons/tab.svg",
432 IconName::Terminal => "icons/terminal.svg",
433 IconName::TextCursor => "icons/text-cursor.svg",
434 IconName::TextSelect => "icons/text_select.svg",
435 IconName::Trash => "icons/trash.svg",
436 IconName::TriangleRight => "icons/triangle_right.svg",
437 IconName::Unpin => "icons/unpin.svg",
438 IconName::Update => "icons/update.svg",
439 IconName::Undo => "icons/undo.svg",
440 IconName::WholeWord => "icons/word_search.svg",
441 IconName::XCircle => "icons/error.svg",
442 IconName::ZedAssistant => "icons/zed_assistant.svg",
443 IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
444 IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
445 IconName::Visible => "icons/visible.svg",
446 }
447 }
448}
449
450#[derive(IntoElement)]
451pub struct Icon {
452 path: SharedString,
453 color: Color,
454 size: Rems,
455 transformation: Transformation,
456}
457
458impl Icon {
459 pub fn new(icon: IconName) -> Self {
460 Self {
461 path: icon.path().into(),
462 color: Color::default(),
463 size: IconSize::default().rems(),
464 transformation: Transformation::default(),
465 }
466 }
467
468 pub fn from_path(path: impl Into<SharedString>) -> Self {
469 Self {
470 path: path.into(),
471 color: Color::default(),
472 size: IconSize::default().rems(),
473 transformation: Transformation::default(),
474 }
475 }
476
477 pub fn color(mut self, color: Color) -> Self {
478 self.color = color;
479 self
480 }
481
482 pub fn size(mut self, size: IconSize) -> Self {
483 self.size = size.rems();
484 self
485 }
486
487 /// Sets a custom size for the icon, in [`Rems`].
488 ///
489 /// Not to be exposed outside of the `ui` crate.
490 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
491 self.size = size;
492 self
493 }
494
495 pub fn transform(mut self, transformation: Transformation) -> Self {
496 self.transformation = transformation;
497 self
498 }
499}
500
501impl RenderOnce for Icon {
502 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
503 svg()
504 .with_transformation(self.transformation)
505 .size(self.size)
506 .flex_none()
507 .path(self.path)
508 .text_color(self.color.color(cx))
509 }
510}
511
512#[derive(IntoElement)]
513pub struct DecoratedIcon {
514 icon: Icon,
515 decoration: IconDecoration,
516 decoration_color: Color,
517 parent_background: Option<Hsla>,
518}
519
520impl DecoratedIcon {
521 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
522 Self {
523 icon,
524 decoration,
525 decoration_color: Color::Default,
526 parent_background: None,
527 }
528 }
529
530 pub fn decoration_color(mut self, color: Color) -> Self {
531 self.decoration_color = color;
532 self
533 }
534
535 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
536 self.parent_background = background;
537 self
538 }
539}
540
541impl RenderOnce for DecoratedIcon {
542 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
543 let background = self
544 .parent_background
545 .unwrap_or(cx.theme().colors().background);
546
547 let size = self.icon.size;
548
549 let decoration_icon = match self.decoration {
550 IconDecoration::Strikethrough => IconName::Strikethrough,
551 IconDecoration::IndicatorDot => IconName::Indicator,
552 IconDecoration::X => IconName::IndicatorX,
553 };
554
555 let decoration_svg = |icon: IconName| {
556 svg()
557 .absolute()
558 .top_0()
559 .left_0()
560 .path(icon.path())
561 .size(size)
562 .flex_none()
563 .text_color(self.decoration_color.color(cx))
564 };
565
566 let decoration_knockout = |icon: IconName| {
567 svg()
568 .absolute()
569 .top(-rems_from_px(2.))
570 .left(-rems_from_px(3.))
571 .path(icon.path())
572 .size(size + rems_from_px(2.))
573 .flex_none()
574 .text_color(background)
575 };
576
577 div()
578 .relative()
579 .size(self.icon.size)
580 .child(self.icon)
581 .child(decoration_knockout(decoration_icon))
582 .child(decoration_svg(decoration_icon))
583 }
584}
585
586#[derive(IntoElement)]
587pub struct IconWithIndicator {
588 icon: Icon,
589 indicator: Option<Indicator>,
590 indicator_border_color: Option<Hsla>,
591}
592
593impl IconWithIndicator {
594 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
595 Self {
596 icon,
597 indicator,
598 indicator_border_color: None,
599 }
600 }
601
602 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
603 self.indicator = indicator;
604 self
605 }
606
607 pub fn indicator_color(mut self, color: Color) -> Self {
608 if let Some(indicator) = self.indicator.as_mut() {
609 indicator.color = color;
610 }
611 self
612 }
613
614 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
615 self.indicator_border_color = color;
616 self
617 }
618}
619
620impl RenderOnce for IconWithIndicator {
621 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
622 let indicator_border_color = self
623 .indicator_border_color
624 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
625
626 div()
627 .relative()
628 .child(self.icon)
629 .when_some(self.indicator, |this, indicator| {
630 this.child(
631 div()
632 .absolute()
633 .w_2()
634 .h_2()
635 .border_1()
636 .border_color(indicator_border_color)
637 .rounded_full()
638 .bottom_neg_0p5()
639 .right_neg_1()
640 .child(indicator),
641 )
642 })
643 }
644}