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