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