1#![allow(missing_docs)]
2
3mod decorated_icon;
4mod icon_decoration;
5
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9pub use decorated_icon::*;
10use gpui::{img, svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
11pub use icon_decoration::*;
12use serde::{Deserialize, Serialize};
13use strum::{EnumIter, EnumString, IntoStaticStr};
14use ui_macros::DerivePathStr;
15
16use crate::{
17 prelude::*,
18 traits::component_preview::{ComponentExample, ComponentPreview},
19 Indicator,
20};
21
22#[derive(IntoElement)]
23pub enum AnyIcon {
24 Icon(Icon),
25 AnimatedIcon(AnimationElement<Icon>),
26}
27
28impl AnyIcon {
29 /// Returns a new [`AnyIcon`] after applying the given mapping function
30 /// to the contained [`Icon`].
31 pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
32 match self {
33 Self::Icon(icon) => Self::Icon(f(icon)),
34 Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
35 }
36 }
37}
38
39impl From<Icon> for AnyIcon {
40 fn from(value: Icon) -> Self {
41 Self::Icon(value)
42 }
43}
44
45impl From<AnimationElement<Icon>> for AnyIcon {
46 fn from(value: AnimationElement<Icon>) -> Self {
47 Self::AnimatedIcon(value)
48 }
49}
50
51impl RenderOnce for AnyIcon {
52 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
53 match self {
54 Self::Icon(icon) => icon.into_any_element(),
55 Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
56 }
57 }
58}
59
60#[derive(Default, PartialEq, Copy, Clone)]
61pub enum IconSize {
62 /// 10px
63 Indicator,
64 /// 12px
65 XSmall,
66 /// 14px
67 Small,
68 #[default]
69 /// 16px
70 Medium,
71 /// 48px
72 XLarge,
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 IconSize::XLarge => rems_from_px(48.),
83 }
84 }
85
86 /// Returns the individual components of the square that contains this [`IconSize`].
87 ///
88 /// The returned tuple contains:
89 /// 1. The length of one side of the square
90 /// 2. The padding of one side of the square
91 pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
92 let icon_size = self.rems() * cx.rem_size();
93 let padding = match self {
94 IconSize::Indicator => DynamicSpacing::Base00.px(cx),
95 IconSize::XSmall => DynamicSpacing::Base02.px(cx),
96 IconSize::Small => DynamicSpacing::Base02.px(cx),
97 IconSize::Medium => DynamicSpacing::Base02.px(cx),
98 IconSize::XLarge => DynamicSpacing::Base02.px(cx),
99 };
100
101 (icon_size, padding)
102 }
103
104 /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
105 pub fn square(&self, cx: &mut WindowContext) -> Pixels {
106 let (icon_size, padding) = self.square_components(cx);
107
108 icon_size + padding * 2.
109 }
110}
111
112#[derive(
113 Debug,
114 PartialEq,
115 Eq,
116 Copy,
117 Clone,
118 EnumIter,
119 EnumString,
120 IntoStaticStr,
121 Serialize,
122 Deserialize,
123 DerivePathStr,
124)]
125#[strum(serialize_all = "snake_case")]
126#[path_str(prefix = "icons", suffix = ".svg")]
127pub enum IconName {
128 Ai,
129 AiAnthropic,
130 AiAnthropicHosted,
131 AiGoogle,
132 AiLmStudio,
133 AiOllama,
134 AiOpenAi,
135 AiZed,
136 ArrowCircle,
137 ArrowDown,
138 ArrowDownFromLine,
139 ArrowLeft,
140 ArrowRight,
141 ArrowUp,
142 ArrowUpFromLine,
143 ArrowUpRight,
144 AtSign,
145 AudioOff,
146 AudioOn,
147 Backspace,
148 Bell,
149 BellDot,
150 BellOff,
151 BellRing,
152 Blocks,
153 Bolt,
154 Book,
155 BookCopy,
156 BookPlus,
157 CaseSensitive,
158 Check,
159 ChevronDown,
160 /// This chevron indicates a popover menu.
161 ChevronDownSmall,
162 ChevronLeft,
163 ChevronRight,
164 ChevronUp,
165 ChevronUpDown,
166 Close,
167 Code,
168 Command,
169 Context,
170 Control,
171 Copilot,
172 CopilotDisabled,
173 CopilotError,
174 CopilotInit,
175 Copy,
176 CountdownTimer,
177 CursorIBeam,
178 Dash,
179 DatabaseZap,
180 Delete,
181 Diff,
182 Disconnected,
183 Download,
184 Ellipsis,
185 EllipsisVertical,
186 Envelope,
187 Eraser,
188 Escape,
189 ExpandVertical,
190 Exit,
191 ExternalLink,
192 Eye,
193 File,
194 FileCode,
195 FileDoc,
196 FileDiff,
197 FileGeneric,
198 FileGit,
199 FileLock,
200 FileRust,
201 FileSearch,
202 FileText,
203 FileToml,
204 FileTree,
205 Filter,
206 Folder,
207 FolderOpen,
208 FolderX,
209 Font,
210 FontSize,
211 FontWeight,
212 GenericClose,
213 GenericMaximize,
214 GenericMinimize,
215 GenericRestore,
216 Github,
217 Globe,
218 GitBranch,
219 Hash,
220 HistoryRerun,
221 Indicator,
222 IndicatorX,
223 Info,
224 InlayHint,
225 Keyboard,
226 Library,
227 LineHeight,
228 Link,
229 ListTree,
230 ListX,
231 MagnifyingGlass,
232 MailOpen,
233 Maximize,
234 Menu,
235 MessageBubbles,
236 MessageCircle,
237 Mic,
238 MicMute,
239 Microscope,
240 Minimize,
241 Option,
242 PageDown,
243 PageUp,
244 PanelLeft,
245 PanelRight,
246 Pencil,
247 Person,
248 PersonCircle,
249 PhoneIncoming,
250 Pin,
251 Play,
252 Plus,
253 PocketKnife,
254 Public,
255 PullRequest,
256 Quote,
257 RefreshTitle,
258 Regex,
259 ReplNeutral,
260 Replace,
261 ReplaceAll,
262 ReplaceNext,
263 ReplyArrowRight,
264 Rerun,
265 Return,
266 Reveal,
267 RotateCcw,
268 RotateCw,
269 Route,
270 Save,
271 Screen,
272 SearchCode,
273 SearchSelection,
274 SelectAll,
275 Server,
276 Settings,
277 SettingsAlt,
278 Shift,
279 Slash,
280 SlashSquare,
281 Sliders,
282 SlidersVertical,
283 Snip,
284 Space,
285 Sparkle,
286 SparkleAlt,
287 SparkleFilled,
288 Spinner,
289 Split,
290 SquareDot,
291 SquareMinus,
292 SquarePlus,
293 Star,
294 StarFilled,
295 Stop,
296 Strikethrough,
297 Supermaven,
298 SupermavenDisabled,
299 SupermavenError,
300 SupermavenInit,
301 SwatchBook,
302 Tab,
303 Terminal,
304 TextSnippet,
305 ThumbsUp,
306 ThumbsDown,
307 Trash,
308 TrashAlt,
309 Triangle,
310 TriangleRight,
311 Undo,
312 Unpin,
313 Update,
314 UserGroup,
315 Visible,
316 Wand,
317 Warning,
318 WholeWord,
319 X,
320 XCircle,
321 ZedAssistant,
322 ZedAssistant2,
323 ZedAssistantFilled,
324 ZedPredict,
325 ZedXCopilot,
326}
327
328impl From<IconName> for Icon {
329 fn from(icon: IconName) -> Self {
330 Icon::new(icon)
331 }
332}
333
334/// The source of an icon.
335enum IconSource {
336 /// An SVG embedded in the Zed binary.
337 Svg(SharedString),
338 /// An image file located at the specified path.
339 ///
340 /// Currently our SVG renderer is missing support for the following features:
341 /// 1. Loading SVGs from external files.
342 /// 2. Rendering polychrome SVGs.
343 ///
344 /// In order to support icon themes, we render the icons as images instead.
345 Image(Arc<Path>),
346}
347
348impl IconSource {
349 fn from_path(path: impl Into<SharedString>) -> Self {
350 let path = path.into();
351 if path.starts_with("icons/file_icons") {
352 Self::Svg(path)
353 } else {
354 Self::Image(Arc::from(PathBuf::from(path.as_ref())))
355 }
356 }
357}
358
359#[derive(IntoElement)]
360pub struct Icon {
361 source: IconSource,
362 color: Color,
363 size: Rems,
364 transformation: Transformation,
365}
366
367impl Icon {
368 pub fn new(icon: IconName) -> Self {
369 Self {
370 source: IconSource::Svg(icon.path().into()),
371 color: Color::default(),
372 size: IconSize::default().rems(),
373 transformation: Transformation::default(),
374 }
375 }
376
377 pub fn from_path(path: impl Into<SharedString>) -> Self {
378 Self {
379 source: IconSource::from_path(path),
380 color: Color::default(),
381 size: IconSize::default().rems(),
382 transformation: Transformation::default(),
383 }
384 }
385
386 pub fn color(mut self, color: Color) -> Self {
387 self.color = color;
388 self
389 }
390
391 pub fn size(mut self, size: IconSize) -> Self {
392 self.size = size.rems();
393 self
394 }
395
396 /// Sets a custom size for the icon, in [`Rems`].
397 ///
398 /// Not to be exposed outside of the `ui` crate.
399 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
400 self.size = size;
401 self
402 }
403
404 pub fn transform(mut self, transformation: Transformation) -> Self {
405 self.transformation = transformation;
406 self
407 }
408}
409
410impl RenderOnce for Icon {
411 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
412 match self.source {
413 IconSource::Svg(path) => svg()
414 .with_transformation(self.transformation)
415 .size(self.size)
416 .flex_none()
417 .path(path)
418 .text_color(self.color.color(cx))
419 .into_any_element(),
420 IconSource::Image(path) => img(path)
421 .size(self.size)
422 .flex_none()
423 .text_color(self.color.color(cx))
424 .into_any_element(),
425 }
426 }
427}
428
429#[derive(IntoElement)]
430pub struct IconWithIndicator {
431 icon: Icon,
432 indicator: Option<Indicator>,
433 indicator_border_color: Option<Hsla>,
434}
435
436impl IconWithIndicator {
437 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
438 Self {
439 icon,
440 indicator,
441 indicator_border_color: None,
442 }
443 }
444
445 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
446 self.indicator = indicator;
447 self
448 }
449
450 pub fn indicator_color(mut self, color: Color) -> Self {
451 if let Some(indicator) = self.indicator.as_mut() {
452 indicator.color = color;
453 }
454 self
455 }
456
457 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
458 self.indicator_border_color = color;
459 self
460 }
461}
462
463impl RenderOnce for IconWithIndicator {
464 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
465 let indicator_border_color = self
466 .indicator_border_color
467 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
468
469 div()
470 .relative()
471 .child(self.icon)
472 .when_some(self.indicator, |this, indicator| {
473 this.child(
474 div()
475 .absolute()
476 .size_2p5()
477 .border_2()
478 .border_color(indicator_border_color)
479 .rounded_full()
480 .bottom_neg_0p5()
481 .right_neg_0p5()
482 .child(indicator),
483 )
484 })
485 }
486}
487
488impl ComponentPreview for Icon {
489 fn examples(_cx: &mut WindowContext) -> Vec<ComponentExampleGroup<Icon>> {
490 let arrow_icons = vec![
491 IconName::ArrowDown,
492 IconName::ArrowLeft,
493 IconName::ArrowRight,
494 IconName::ArrowUp,
495 IconName::ArrowCircle,
496 ];
497
498 vec![example_group_with_title(
499 "Arrow Icons",
500 arrow_icons
501 .into_iter()
502 .map(|icon| {
503 let name = format!("{:?}", icon).to_string();
504 ComponentExample::new(name, Icon::new(icon))
505 })
506 .collect(),
507 )]
508 }
509}