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