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