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