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