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