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