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