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