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