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