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