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 RefreshTitle,
238 Regex,
239 ReplNeutral,
240 Replace,
241 ReplaceAll,
242 ReplaceNext,
243 ReplyArrowRight,
244 Rerun,
245 Return,
246 Reveal,
247 RotateCcw,
248 RotateCw,
249 Route,
250 Save,
251 Screen,
252 SearchCode,
253 SearchSelection,
254 SelectAll,
255 Server,
256 Settings,
257 SettingsAlt,
258 Shift,
259 Slash,
260 SlashSquare,
261 Sliders,
262 SlidersVertical,
263 Snip,
264 Space,
265 Sparkle,
266 SparkleAlt,
267 SparkleFilled,
268 Spinner,
269 Split,
270 Star,
271 StarFilled,
272 Stop,
273 Strikethrough,
274 Supermaven,
275 SupermavenDisabled,
276 SupermavenError,
277 SupermavenInit,
278 Tab,
279 Terminal,
280 Trash,
281 TrashAlt,
282 TriangleRight,
283 Undo,
284 Unpin,
285 Update,
286 UserGroup,
287 Visible,
288 Wand,
289 Warning,
290 WholeWord,
291 XCircle,
292 ZedAssistant,
293 ZedAssistantFilled,
294 ZedXCopilot,
295}
296
297impl From<IconName> for Icon {
298 fn from(icon: IconName) -> Self {
299 Icon::new(icon)
300 }
301}
302
303#[derive(IntoElement)]
304pub struct Icon {
305 path: SharedString,
306 color: Color,
307 size: Rems,
308 transformation: Transformation,
309}
310
311impl Icon {
312 pub fn new(icon: IconName) -> Self {
313 Self {
314 path: icon.path().into(),
315 color: Color::default(),
316 size: IconSize::default().rems(),
317 transformation: Transformation::default(),
318 }
319 }
320
321 pub fn from_path(path: impl Into<SharedString>) -> Self {
322 Self {
323 path: path.into(),
324 color: Color::default(),
325 size: IconSize::default().rems(),
326 transformation: Transformation::default(),
327 }
328 }
329
330 pub fn color(mut self, color: Color) -> Self {
331 self.color = color;
332 self
333 }
334
335 pub fn size(mut self, size: IconSize) -> Self {
336 self.size = size.rems();
337 self
338 }
339
340 /// Sets a custom size for the icon, in [`Rems`].
341 ///
342 /// Not to be exposed outside of the `ui` crate.
343 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
344 self.size = size;
345 self
346 }
347
348 pub fn transform(mut self, transformation: Transformation) -> Self {
349 self.transformation = transformation;
350 self
351 }
352}
353
354impl RenderOnce for Icon {
355 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
356 svg()
357 .with_transformation(self.transformation)
358 .size(self.size)
359 .flex_none()
360 .path(self.path)
361 .text_color(self.color.color(cx))
362 }
363}
364
365#[derive(IntoElement)]
366pub struct DecoratedIcon {
367 icon: Icon,
368 decoration: IconDecoration,
369 decoration_color: Color,
370 parent_background: Option<Hsla>,
371}
372
373impl DecoratedIcon {
374 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
375 Self {
376 icon,
377 decoration,
378 decoration_color: Color::Default,
379 parent_background: None,
380 }
381 }
382
383 pub fn decoration_color(mut self, color: Color) -> Self {
384 self.decoration_color = color;
385 self
386 }
387
388 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
389 self.parent_background = background;
390 self
391 }
392}
393
394impl RenderOnce for DecoratedIcon {
395 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
396 let background = self
397 .parent_background
398 .unwrap_or(cx.theme().colors().background);
399
400 let size = self.icon.size;
401
402 let decoration_icon = match self.decoration {
403 IconDecoration::Strikethrough => IconName::Strikethrough,
404 IconDecoration::IndicatorDot => IconName::Indicator,
405 IconDecoration::X => IconName::IndicatorX,
406 };
407
408 let decoration_svg = |icon: IconName| {
409 svg()
410 .absolute()
411 .top_0()
412 .left_0()
413 .path(icon.path())
414 .size(size)
415 .flex_none()
416 .text_color(self.decoration_color.color(cx))
417 };
418
419 let decoration_knockout = |icon: IconName| {
420 svg()
421 .absolute()
422 .top(-rems_from_px(2.))
423 .left(-rems_from_px(3.))
424 .path(icon.path())
425 .size(size + rems_from_px(2.))
426 .flex_none()
427 .text_color(background)
428 };
429
430 div()
431 .relative()
432 .size(self.icon.size)
433 .child(self.icon)
434 .child(decoration_knockout(decoration_icon))
435 .child(decoration_svg(decoration_icon))
436 }
437}
438
439#[derive(IntoElement)]
440pub struct IconWithIndicator {
441 icon: Icon,
442 indicator: Option<Indicator>,
443 indicator_border_color: Option<Hsla>,
444}
445
446impl IconWithIndicator {
447 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
448 Self {
449 icon,
450 indicator,
451 indicator_border_color: None,
452 }
453 }
454
455 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
456 self.indicator = indicator;
457 self
458 }
459
460 pub fn indicator_color(mut self, color: Color) -> Self {
461 if let Some(indicator) = self.indicator.as_mut() {
462 indicator.color = color;
463 }
464 self
465 }
466
467 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
468 self.indicator_border_color = color;
469 self
470 }
471}
472
473impl RenderOnce for IconWithIndicator {
474 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
475 let indicator_border_color = self
476 .indicator_border_color
477 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
478
479 div()
480 .relative()
481 .child(self.icon)
482 .when_some(self.indicator, |this, indicator| {
483 this.child(
484 div()
485 .absolute()
486 .size_2p5()
487 .border_2()
488 .border_color(indicator_border_color)
489 .rounded_full()
490 .bottom_neg_0p5()
491 .right_neg_0p5()
492 .child(indicator),
493 )
494 })
495 }
496}