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