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 transform(mut self, transformation: Transformation) -> Self {
352 self.transformation = transformation;
353 self
354 }
355}
356
357impl RenderOnce for Icon {
358 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
359 svg()
360 .with_transformation(self.transformation)
361 .size(self.size)
362 .flex_none()
363 .path(self.path)
364 .text_color(self.color.color(cx))
365 }
366}
367
368#[derive(IntoElement)]
369pub struct DecoratedIcon {
370 icon: Icon,
371 decoration: IconDecoration,
372 decoration_color: Color,
373 parent_background: Option<Hsla>,
374}
375
376impl DecoratedIcon {
377 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
378 Self {
379 icon,
380 decoration,
381 decoration_color: Color::Default,
382 parent_background: None,
383 }
384 }
385
386 pub fn decoration_color(mut self, color: Color) -> Self {
387 self.decoration_color = color;
388 self
389 }
390
391 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
392 self.parent_background = background;
393 self
394 }
395}
396
397impl RenderOnce for DecoratedIcon {
398 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
399 let background = self
400 .parent_background
401 .unwrap_or(cx.theme().colors().background);
402
403 let size = self.icon.size;
404
405 let decoration_icon = match self.decoration {
406 IconDecoration::Strikethrough => IconName::Strikethrough,
407 IconDecoration::IndicatorDot => IconName::Indicator,
408 IconDecoration::X => IconName::IndicatorX,
409 };
410
411 let decoration_svg = |icon: IconName| {
412 svg()
413 .absolute()
414 .top_0()
415 .left_0()
416 .path(icon.path())
417 .size(size)
418 .flex_none()
419 .text_color(self.decoration_color.color(cx))
420 };
421
422 let decoration_knockout = |icon: IconName| {
423 svg()
424 .absolute()
425 .top(-rems_from_px(2.))
426 .left(-rems_from_px(3.))
427 .path(icon.path())
428 .size(size + rems_from_px(2.))
429 .flex_none()
430 .text_color(background)
431 };
432
433 div()
434 .relative()
435 .size(self.icon.size)
436 .child(self.icon)
437 .child(decoration_knockout(decoration_icon))
438 .child(decoration_svg(decoration_icon))
439 }
440}
441
442#[derive(IntoElement)]
443pub struct IconWithIndicator {
444 icon: Icon,
445 indicator: Option<Indicator>,
446 indicator_border_color: Option<Hsla>,
447}
448
449impl IconWithIndicator {
450 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
451 Self {
452 icon,
453 indicator,
454 indicator_border_color: None,
455 }
456 }
457
458 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
459 self.indicator = indicator;
460 self
461 }
462
463 pub fn indicator_color(mut self, color: Color) -> Self {
464 if let Some(indicator) = self.indicator.as_mut() {
465 indicator.color = color;
466 }
467 self
468 }
469
470 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
471 self.indicator_border_color = color;
472 self
473 }
474}
475
476impl RenderOnce for IconWithIndicator {
477 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
478 let indicator_border_color = self
479 .indicator_border_color
480 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
481
482 div()
483 .relative()
484 .child(self.icon)
485 .when_some(self.indicator, |this, indicator| {
486 this.child(
487 div()
488 .absolute()
489 .w_2()
490 .h_2()
491 .border_1()
492 .border_color(indicator_border_color)
493 .rounded_full()
494 .bottom_neg_0p5()
495 .right_neg_1()
496 .child(indicator),
497 )
498 })
499 }
500}