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