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