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, _window: &mut Window, _cx: &mut App) -> 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, window: &mut Window, cx: &mut App) -> (Pixels, Pixels) {
92 let icon_size = self.rems() * window.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, window: &mut Window, cx: &mut App) -> Pixels {
106 let (icon_size, padding) = self.square_components(window, 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 AiDeepSeek,
132 AiGoogle,
133 AiLmStudio,
134 AiOllama,
135 AiOpenAi,
136 AiZed,
137 ArrowCircle,
138 ArrowDown,
139 ArrowDownFromLine,
140 ArrowLeft,
141 ArrowRight,
142 ArrowUp,
143 ArrowUpFromLine,
144 ArrowUpRight,
145 AtSign,
146 AudioOff,
147 AudioOn,
148 Backspace,
149 Bell,
150 BellDot,
151 BellOff,
152 BellRing,
153 Blocks,
154 Bolt,
155 Book,
156 BookCopy,
157 BookPlus,
158 CaseSensitive,
159 Check,
160 ChevronDown,
161 /// This chevron indicates a popover menu.
162 ChevronDownSmall,
163 ChevronLeft,
164 ChevronRight,
165 ChevronUp,
166 ChevronUpDown,
167 Circle,
168 Close,
169 Code,
170 Command,
171 Context,
172 Control,
173 Copilot,
174 CopilotDisabled,
175 CopilotError,
176 CopilotInit,
177 Copy,
178 CountdownTimer,
179 CursorIBeam,
180 Dash,
181 DatabaseZap,
182 Delete,
183 Diff,
184 Disconnected,
185 Download,
186 Ellipsis,
187 EllipsisVertical,
188 Envelope,
189 Eraser,
190 Escape,
191 ExpandVertical,
192 Exit,
193 ExternalLink,
194 Eye,
195 File,
196 FileCode,
197 FileDoc,
198 FileDiff,
199 FileGeneric,
200 FileGit,
201 FileLock,
202 FileRust,
203 FileSearch,
204 FileText,
205 FileToml,
206 FileTree,
207 Filter,
208 Folder,
209 FolderOpen,
210 FolderX,
211 Font,
212 FontSize,
213 FontWeight,
214 GenericClose,
215 GenericMaximize,
216 GenericMinimize,
217 GenericRestore,
218 Github,
219 Globe,
220 GitBranch,
221 Hash,
222 HistoryRerun,
223 Indicator,
224 IndicatorX,
225 Info,
226 InlayHint,
227 Keyboard,
228 Library,
229 LineHeight,
230 Link,
231 ListTree,
232 ListX,
233 MagnifyingGlass,
234 MailOpen,
235 Maximize,
236 Menu,
237 MessageBubbles,
238 MessageCircle,
239 Mic,
240 MicMute,
241 Microscope,
242 Minimize,
243 Option,
244 PageDown,
245 PageUp,
246 PanelLeft,
247 PanelRight,
248 Pencil,
249 Person,
250 PersonCircle,
251 PhoneIncoming,
252 Pin,
253 Play,
254 Plus,
255 PocketKnife,
256 Public,
257 PullRequest,
258 Quote,
259 RefreshTitle,
260 Regex,
261 ReplNeutral,
262 Replace,
263 ReplaceAll,
264 ReplaceNext,
265 ReplyArrowRight,
266 Rerun,
267 Return,
268 Reveal,
269 RotateCcw,
270 RotateCw,
271 Route,
272 Save,
273 Screen,
274 SearchCode,
275 SearchSelection,
276 SelectAll,
277 Server,
278 Settings,
279 SettingsAlt,
280 Shift,
281 Slash,
282 SlashSquare,
283 Sliders,
284 SlidersVertical,
285 Snip,
286 Space,
287 Sparkle,
288 SparkleAlt,
289 SparkleFilled,
290 Spinner,
291 Split,
292 SquareDot,
293 SquareMinus,
294 SquarePlus,
295 Star,
296 StarFilled,
297 Stop,
298 Strikethrough,
299 Supermaven,
300 SupermavenDisabled,
301 SupermavenError,
302 SupermavenInit,
303 SwatchBook,
304 Tab,
305 Terminal,
306 TextSnippet,
307 ThumbsUp,
308 ThumbsDown,
309 Trash,
310 TrashAlt,
311 Triangle,
312 TriangleRight,
313 Undo,
314 Unpin,
315 Update,
316 UserGroup,
317 Visible,
318 Wand,
319 Warning,
320 WholeWord,
321 X,
322 XCircle,
323 ZedAssistant,
324 ZedAssistant2,
325 ZedAssistantFilled,
326 ZedPredict,
327 ZedPredictDisabled,
328 ZedXCopilot,
329}
330
331impl From<IconName> for Icon {
332 fn from(icon: IconName) -> Self {
333 Icon::new(icon)
334 }
335}
336
337/// The source of an icon.
338enum IconSource {
339 /// An SVG embedded in the Zed binary.
340 Svg(SharedString),
341 /// An image file located at the specified path.
342 ///
343 /// Currently our SVG renderer is missing support for the following features:
344 /// 1. Loading SVGs from external files.
345 /// 2. Rendering polychrome SVGs.
346 ///
347 /// In order to support icon themes, we render the icons as images instead.
348 Image(Arc<Path>),
349}
350
351impl IconSource {
352 fn from_path(path: impl Into<SharedString>) -> Self {
353 let path = path.into();
354 if path.starts_with("icons/file_icons") {
355 Self::Svg(path)
356 } else {
357 Self::Image(Arc::from(PathBuf::from(path.as_ref())))
358 }
359 }
360}
361
362#[derive(IntoElement)]
363pub struct Icon {
364 source: IconSource,
365 color: Color,
366 size: Rems,
367 transformation: Transformation,
368}
369
370impl Icon {
371 pub fn new(icon: IconName) -> Self {
372 Self {
373 source: IconSource::Svg(icon.path().into()),
374 color: Color::default(),
375 size: IconSize::default().rems(),
376 transformation: Transformation::default(),
377 }
378 }
379
380 pub fn from_path(path: impl Into<SharedString>) -> Self {
381 Self {
382 source: IconSource::from_path(path),
383 color: Color::default(),
384 size: IconSize::default().rems(),
385 transformation: Transformation::default(),
386 }
387 }
388
389 pub fn color(mut self, color: Color) -> Self {
390 self.color = color;
391 self
392 }
393
394 pub fn size(mut self, size: IconSize) -> Self {
395 self.size = size.rems();
396 self
397 }
398
399 /// Sets a custom size for the icon, in [`Rems`].
400 ///
401 /// Not to be exposed outside of the `ui` crate.
402 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
403 self.size = size;
404 self
405 }
406
407 pub fn transform(mut self, transformation: Transformation) -> Self {
408 self.transformation = transformation;
409 self
410 }
411}
412
413impl RenderOnce for Icon {
414 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
415 match self.source {
416 IconSource::Svg(path) => svg()
417 .with_transformation(self.transformation)
418 .size(self.size)
419 .flex_none()
420 .path(path)
421 .text_color(self.color.color(cx))
422 .into_any_element(),
423 IconSource::Image(path) => img(path)
424 .size(self.size)
425 .flex_none()
426 .text_color(self.color.color(cx))
427 .into_any_element(),
428 }
429 }
430}
431
432#[derive(IntoElement)]
433pub struct IconWithIndicator {
434 icon: Icon,
435 indicator: Option<Indicator>,
436 indicator_border_color: Option<Hsla>,
437}
438
439impl IconWithIndicator {
440 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
441 Self {
442 icon,
443 indicator,
444 indicator_border_color: None,
445 }
446 }
447
448 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
449 self.indicator = indicator;
450 self
451 }
452
453 pub fn indicator_color(mut self, color: Color) -> Self {
454 if let Some(indicator) = self.indicator.as_mut() {
455 indicator.color = color;
456 }
457 self
458 }
459
460 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
461 self.indicator_border_color = color;
462 self
463 }
464}
465
466impl RenderOnce for IconWithIndicator {
467 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
468 let indicator_border_color = self
469 .indicator_border_color
470 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
471
472 div()
473 .relative()
474 .child(self.icon)
475 .when_some(self.indicator, |this, indicator| {
476 this.child(
477 div()
478 .absolute()
479 .size_2p5()
480 .border_2()
481 .border_color(indicator_border_color)
482 .rounded_full()
483 .bottom_neg_0p5()
484 .right_neg_0p5()
485 .child(indicator),
486 )
487 })
488 }
489}
490
491impl ComponentPreview for Icon {
492 fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Icon>> {
493 let arrow_icons = vec![
494 IconName::ArrowDown,
495 IconName::ArrowLeft,
496 IconName::ArrowRight,
497 IconName::ArrowUp,
498 IconName::ArrowCircle,
499 ];
500
501 vec![example_group_with_title(
502 "Arrow Icons",
503 arrow_icons
504 .into_iter()
505 .map(|icon| {
506 let name = format!("{:?}", icon).to_string();
507 ComponentExample::new(name, Icon::new(icon))
508 })
509 .collect(),
510 )]
511 }
512}