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 Wand,
288 Warning,
289 WholeWord,
290 XCircle,
291 ZedAssistant,
292 ZedAssistantFilled,
293 ZedXCopilot,
294}
295
296impl From<IconName> for Icon {
297 fn from(icon: IconName) -> Self {
298 Icon::new(icon)
299 }
300}
301
302#[derive(IntoElement)]
303pub struct Icon {
304 path: SharedString,
305 color: Color,
306 size: Rems,
307 transformation: Transformation,
308}
309
310impl Icon {
311 pub fn new(icon: IconName) -> Self {
312 Self {
313 path: icon.path().into(),
314 color: Color::default(),
315 size: IconSize::default().rems(),
316 transformation: Transformation::default(),
317 }
318 }
319
320 pub fn from_path(path: impl Into<SharedString>) -> Self {
321 Self {
322 path: path.into(),
323 color: Color::default(),
324 size: IconSize::default().rems(),
325 transformation: Transformation::default(),
326 }
327 }
328
329 pub fn color(mut self, color: Color) -> Self {
330 self.color = color;
331 self
332 }
333
334 pub fn size(mut self, size: IconSize) -> Self {
335 self.size = size.rems();
336 self
337 }
338
339 /// Sets a custom size for the icon, in [`Rems`].
340 ///
341 /// Not to be exposed outside of the `ui` crate.
342 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
343 self.size = size;
344 self
345 }
346
347 pub fn transform(mut self, transformation: Transformation) -> Self {
348 self.transformation = transformation;
349 self
350 }
351}
352
353impl RenderOnce for Icon {
354 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
355 svg()
356 .with_transformation(self.transformation)
357 .size(self.size)
358 .flex_none()
359 .path(self.path)
360 .text_color(self.color.color(cx))
361 }
362}
363
364#[derive(IntoElement)]
365pub struct DecoratedIcon {
366 icon: Icon,
367 decoration: IconDecoration,
368 decoration_color: Color,
369 parent_background: Option<Hsla>,
370}
371
372impl DecoratedIcon {
373 pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
374 Self {
375 icon,
376 decoration,
377 decoration_color: Color::Default,
378 parent_background: None,
379 }
380 }
381
382 pub fn decoration_color(mut self, color: Color) -> Self {
383 self.decoration_color = color;
384 self
385 }
386
387 pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
388 self.parent_background = background;
389 self
390 }
391}
392
393impl RenderOnce for DecoratedIcon {
394 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
395 let background = self
396 .parent_background
397 .unwrap_or(cx.theme().colors().background);
398
399 let size = self.icon.size;
400
401 let decoration_icon = match self.decoration {
402 IconDecoration::Strikethrough => IconName::Strikethrough,
403 IconDecoration::IndicatorDot => IconName::Indicator,
404 IconDecoration::X => IconName::IndicatorX,
405 };
406
407 let decoration_svg = |icon: IconName| {
408 svg()
409 .absolute()
410 .top_0()
411 .left_0()
412 .path(icon.path())
413 .size(size)
414 .flex_none()
415 .text_color(self.decoration_color.color(cx))
416 };
417
418 let decoration_knockout = |icon: IconName| {
419 svg()
420 .absolute()
421 .top(-rems_from_px(2.))
422 .left(-rems_from_px(3.))
423 .path(icon.path())
424 .size(size + rems_from_px(2.))
425 .flex_none()
426 .text_color(background)
427 };
428
429 div()
430 .relative()
431 .size(self.icon.size)
432 .child(self.icon)
433 .child(decoration_knockout(decoration_icon))
434 .child(decoration_svg(decoration_icon))
435 }
436}
437
438#[derive(IntoElement)]
439pub struct IconWithIndicator {
440 icon: Icon,
441 indicator: Option<Indicator>,
442 indicator_border_color: Option<Hsla>,
443}
444
445impl IconWithIndicator {
446 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
447 Self {
448 icon,
449 indicator,
450 indicator_border_color: None,
451 }
452 }
453
454 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
455 self.indicator = indicator;
456 self
457 }
458
459 pub fn indicator_color(mut self, color: Color) -> Self {
460 if let Some(indicator) = self.indicator.as_mut() {
461 indicator.color = color;
462 }
463 self
464 }
465
466 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
467 self.indicator_border_color = color;
468 self
469 }
470}
471
472impl RenderOnce for IconWithIndicator {
473 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
474 let indicator_border_color = self
475 .indicator_border_color
476 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
477
478 div()
479 .relative()
480 .child(self.icon)
481 .when_some(self.indicator, |this, indicator| {
482 this.child(
483 div()
484 .absolute()
485 .size_2p5()
486 .border_2()
487 .border_color(indicator_border_color)
488 .rounded_full()
489 .bottom_neg_0p5()
490 .right_neg_0p5()
491 .child(indicator),
492 )
493 })
494 }
495}