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