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