1#![allow(missing_docs)]
2use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
3use serde::{Deserialize, Serialize};
4use strum::{EnumIter, EnumString, IntoStaticStr};
5use ui_macros::DerivePathStr;
6
7use crate::{
8 prelude::*,
9 traits::component_preview::{ComponentExample, ComponentPreview},
10 Indicator,
11};
12
13#[derive(IntoElement)]
14pub enum AnyIcon {
15 Icon(Icon),
16 AnimatedIcon(AnimationElement<Icon>),
17}
18
19impl AnyIcon {
20 /// Returns a new [`AnyIcon`] after applying the given mapping function
21 /// to the contained [`Icon`].
22 pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
23 match self {
24 Self::Icon(icon) => Self::Icon(f(icon)),
25 Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
26 }
27 }
28}
29
30impl From<Icon> for AnyIcon {
31 fn from(value: Icon) -> Self {
32 Self::Icon(value)
33 }
34}
35
36impl From<AnimationElement<Icon>> for AnyIcon {
37 fn from(value: AnimationElement<Icon>) -> Self {
38 Self::AnimatedIcon(value)
39 }
40}
41
42impl RenderOnce for AnyIcon {
43 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
44 match self {
45 Self::Icon(icon) => icon.into_any_element(),
46 Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
47 }
48 }
49}
50
51/// The decoration for an icon.
52///
53/// For example, this can show an indicator, an "x",
54/// or a diagonal strikethrough to indicate something is disabled.
55#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
56pub enum IconDecoration {
57 Strikethrough,
58 IndicatorDot,
59 X,
60}
61
62#[derive(Default, PartialEq, Copy, Clone)]
63pub enum IconSize {
64 /// 10px
65 Indicator,
66 /// 12px
67 XSmall,
68 /// 14px
69 Small,
70 #[default]
71 /// 16px
72 Medium,
73}
74
75impl IconSize {
76 pub fn rems(self) -> Rems {
77 match self {
78 IconSize::Indicator => rems_from_px(10.),
79 IconSize::XSmall => rems_from_px(12.),
80 IconSize::Small => rems_from_px(14.),
81 IconSize::Medium => rems_from_px(16.),
82 }
83 }
84
85 /// Returns the individual components of the square that contains this [`IconSize`].
86 ///
87 /// The returned tuple contains:
88 /// 1. The length of one side of the square
89 /// 2. The padding of one side of the square
90 pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
91 let icon_size = self.rems() * cx.rem_size();
92 let padding = match self {
93 IconSize::Indicator => Spacing::None.px(cx),
94 IconSize::XSmall => Spacing::XSmall.px(cx),
95 IconSize::Small => Spacing::XSmall.px(cx),
96 IconSize::Medium => Spacing::XSmall.px(cx),
97 };
98
99 (icon_size, padding)
100 }
101
102 /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
103 pub fn square(&self, cx: &mut WindowContext) -> Pixels {
104 let (icon_size, padding) = self.square_components(cx);
105
106 icon_size + padding * 2.
107 }
108}
109
110#[derive(
111 Debug,
112 PartialEq,
113 Eq,
114 Copy,
115 Clone,
116 EnumIter,
117 EnumString,
118 IntoStaticStr,
119 Serialize,
120 Deserialize,
121 DerivePathStr,
122)]
123#[strum(serialize_all = "snake_case")]
124#[path_str(prefix = "icons", suffix = ".svg")]
125pub enum IconName {
126 Ai,
127 AiAnthropic,
128 AiAnthropicHosted,
129 AiGoogle,
130 AiOllama,
131 AiOpenAi,
132 AiZed,
133 ArrowCircle,
134 ArrowDown,
135 ArrowDownFromLine,
136 ArrowLeft,
137 ArrowRight,
138 ArrowUp,
139 ArrowUpFromLine,
140 ArrowUpRight,
141 AtSign,
142 AudioOff,
143 AudioOn,
144 Backspace,
145 Bell,
146 BellDot,
147 BellOff,
148 BellRing,
149 Bolt,
150 Book,
151 BookCopy,
152 BookPlus,
153 CaseSensitive,
154 Check,
155 ChevronDown,
156 ChevronDownSmall, // This chevron indicates a popover menu.
157 ChevronLeft,
158 ChevronRight,
159 ChevronUp,
160 ChevronUpDown,
161 Close,
162 Code,
163 Command,
164 Context,
165 Control,
166 Copilot,
167 CopilotDisabled,
168 CopilotError,
169 CopilotInit,
170 Copy,
171 CountdownTimer,
172 CursorIBeam,
173 TextSnippet,
174 Dash,
175 DatabaseZap,
176 Delete,
177 Diff,
178 Disconnected,
179 Download,
180 Ellipsis,
181 EllipsisVertical,
182 Envelope,
183 Escape,
184 Exit,
185 ExpandVertical,
186 ExternalLink,
187 Eye,
188 File,
189 FileCode,
190 FileDoc,
191 FileGeneric,
192 FileGit,
193 FileLock,
194 FileRust,
195 FileSearch,
196 FileText,
197 FileToml,
198 FileTree,
199 Filter,
200 Folder,
201 FolderOpen,
202 FolderX,
203 Font,
204 FontSize,
205 FontWeight,
206 GenericClose,
207 GenericMaximize,
208 GenericMinimize,
209 GenericRestore,
210 Github,
211 Hash,
212 HistoryRerun,
213 Indicator,
214 IndicatorX,
215 InlayHint,
216 Library,
217 LineHeight,
218 Link,
219 ListTree,
220 ListX,
221 MagnifyingGlass,
222 MailOpen,
223 Maximize,
224 Menu,
225 MessageBubbles,
226 Mic,
227 MicMute,
228 Microscope,
229 Minimize,
230 Option,
231 PageDown,
232 PageUp,
233 Pencil,
234 Person,
235 Pin,
236 Play,
237 Plus,
238 PocketKnife,
239 Public,
240 PullRequest,
241 Quote,
242 RefreshTitle,
243 Regex,
244 ReplNeutral,
245 Replace,
246 ReplaceAll,
247 ReplaceNext,
248 ReplyArrowRight,
249 Rerun,
250 Return,
251 Reveal,
252 RotateCcw,
253 RotateCw,
254 Route,
255 Save,
256 Screen,
257 SearchCode,
258 SearchSelection,
259 SelectAll,
260 Server,
261 Settings,
262 SettingsAlt,
263 Shift,
264 Slash,
265 SlashSquare,
266 Sliders,
267 SlidersVertical,
268 Snip,
269 Space,
270 Sparkle,
271 SparkleAlt,
272 SparkleFilled,
273 Spinner,
274 Split,
275 Star,
276 StarFilled,
277 Stop,
278 Strikethrough,
279 Supermaven,
280 SupermavenDisabled,
281 SupermavenError,
282 SupermavenInit,
283 Tab,
284 Terminal,
285 Trash,
286 TrashAlt,
287 TriangleRight,
288 Undo,
289 Unpin,
290 Update,
291 UserGroup,
292 Visible,
293 Wand,
294 Warning,
295 WholeWord,
296 XCircle,
297 ZedAssistant,
298 ZedAssistantFilled,
299 ZedXCopilot,
300}
301
302impl From<IconName> for Icon {
303 fn from(icon: IconName) -> Self {
304 Icon::new(icon)
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 .size_2p5()
492 .border_2()
493 .border_color(indicator_border_color)
494 .rounded_full()
495 .bottom_neg_0p5()
496 .right_neg_0p5()
497 .child(indicator),
498 )
499 })
500 }
501}
502
503impl ComponentPreview for Icon {
504 fn examples() -> Vec<ComponentExampleGroup<Icon>> {
505 let arrow_icons = vec![
506 IconName::ArrowDown,
507 IconName::ArrowLeft,
508 IconName::ArrowRight,
509 IconName::ArrowUp,
510 IconName::ArrowCircle,
511 ];
512
513 vec![example_group_with_title(
514 "Arrow Icons",
515 arrow_icons
516 .into_iter()
517 .map(|icon| {
518 let name = format!("{:?}", icon).to_string();
519 ComponentExample::new(name, Icon::new(icon))
520 })
521 .collect(),
522 )]
523 }
524}