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