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