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