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