1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
2use strum::EnumIter;
3
4use crate::{prelude::*, Indicator};
5
6#[derive(IntoElement)]
7pub enum AnyIcon {
8 Icon(Icon),
9 AnimatedIcon(AnimationElement<Icon>),
10}
11
12impl AnyIcon {
13 /// Returns a new [`AnyIcon`] after applying the given mapping function
14 /// to the contained [`Icon`].
15 pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
16 match self {
17 Self::Icon(icon) => Self::Icon(f(icon)),
18 Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
19 }
20 }
21}
22
23impl From<Icon> for AnyIcon {
24 fn from(value: Icon) -> Self {
25 Self::Icon(value)
26 }
27}
28
29impl From<AnimationElement<Icon>> for AnyIcon {
30 fn from(value: AnimationElement<Icon>) -> Self {
31 Self::AnimatedIcon(value)
32 }
33}
34
35impl RenderOnce for AnyIcon {
36 fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
37 match self {
38 Self::Icon(icon) => icon.into_any_element(),
39 Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
40 }
41 }
42}
43
44#[derive(Default, PartialEq, Copy, Clone)]
45pub enum IconSize {
46 Indicator,
47 XSmall,
48 Small,
49 #[default]
50 Medium,
51}
52
53impl IconSize {
54 pub fn rems(self) -> Rems {
55 match self {
56 IconSize::Indicator => rems_from_px(10.),
57 IconSize::XSmall => rems_from_px(12.),
58 IconSize::Small => rems_from_px(14.),
59 IconSize::Medium => rems_from_px(16.),
60 }
61 }
62}
63
64#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
65pub enum IconName {
66 Ai,
67 ArrowDown,
68 ArrowLeft,
69 ArrowRight,
70 ArrowUp,
71 ArrowUpRight,
72 ArrowCircle,
73 AtSign,
74 AudioOff,
75 AudioOn,
76 Backspace,
77 Bell,
78 BellOff,
79 BellRing,
80 BellDot,
81 Bolt,
82 CaseSensitive,
83 Check,
84 ChevronDown,
85 ChevronLeft,
86 ChevronRight,
87 ChevronUp,
88 ExpandVertical,
89 Close,
90 Code,
91 Collab,
92 Command,
93 Control,
94 Copilot,
95 CopilotDisabled,
96 CopilotError,
97 CopilotInit,
98 Copy,
99 Dash,
100 Delete,
101 Disconnected,
102 Ellipsis,
103 Envelope,
104 Escape,
105 ExclamationTriangle,
106 Exit,
107 ExternalLink,
108 File,
109 FileDoc,
110 FileGeneric,
111 FileGit,
112 FileLock,
113 FileRust,
114 FileToml,
115 FileTree,
116 Filter,
117 Folder,
118 FolderOpen,
119 FolderX,
120 Github,
121 Hash,
122 InlayHint,
123 Link,
124 MagicWand,
125 MagnifyingGlass,
126 MailOpen,
127 Maximize,
128 Menu,
129 MessageBubbles,
130 Mic,
131 MicMute,
132 Minimize,
133 Option,
134 PageDown,
135 PageUp,
136 Pencil,
137 Person,
138 Play,
139 Plus,
140 Public,
141 Quote,
142 Regex,
143 Replace,
144 ReplaceAll,
145 ReplaceNext,
146 Return,
147 ReplyArrowRight,
148 Settings,
149 Sliders,
150 Screen,
151 SelectAll,
152 Server,
153 Shift,
154 Snip,
155 Space,
156 Split,
157 Spinner,
158 Tab,
159 Terminal,
160 Trash,
161 Update,
162 WholeWord,
163 XCircle,
164 ZedXCopilot,
165 ZedAssistant,
166 PullRequest,
167}
168
169impl IconName {
170 pub fn path(self) -> &'static str {
171 match self {
172 IconName::Ai => "icons/ai.svg",
173 IconName::ArrowDown => "icons/arrow_down.svg",
174 IconName::ArrowLeft => "icons/arrow_left.svg",
175 IconName::ArrowRight => "icons/arrow_right.svg",
176 IconName::ArrowUp => "icons/arrow_up.svg",
177 IconName::ArrowUpRight => "icons/arrow_up_right.svg",
178 IconName::ArrowCircle => "icons/arrow_circle.svg",
179 IconName::AtSign => "icons/at_sign.svg",
180 IconName::AudioOff => "icons/speaker_off.svg",
181 IconName::AudioOn => "icons/speaker_loud.svg",
182 IconName::Backspace => "icons/backspace.svg",
183 IconName::Bell => "icons/bell.svg",
184 IconName::BellOff => "icons/bell_off.svg",
185 IconName::BellRing => "icons/bell_ring.svg",
186 IconName::BellDot => "icons/bell_dot.svg",
187 IconName::Bolt => "icons/bolt.svg",
188 IconName::CaseSensitive => "icons/case_insensitive.svg",
189 IconName::Check => "icons/check.svg",
190 IconName::ChevronDown => "icons/chevron_down.svg",
191 IconName::ChevronLeft => "icons/chevron_left.svg",
192 IconName::ChevronRight => "icons/chevron_right.svg",
193 IconName::ChevronUp => "icons/chevron_up.svg",
194 IconName::ExpandVertical => "icons/expand_vertical.svg",
195 IconName::Close => "icons/x.svg",
196 IconName::Code => "icons/code.svg",
197 IconName::Collab => "icons/user_group_16.svg",
198 IconName::Command => "icons/command.svg",
199 IconName::Control => "icons/control.svg",
200 IconName::Copilot => "icons/copilot.svg",
201 IconName::CopilotDisabled => "icons/copilot_disabled.svg",
202 IconName::CopilotError => "icons/copilot_error.svg",
203 IconName::CopilotInit => "icons/copilot_init.svg",
204 IconName::Copy => "icons/copy.svg",
205 IconName::Dash => "icons/dash.svg",
206 IconName::Delete => "icons/delete.svg",
207 IconName::Disconnected => "icons/disconnected.svg",
208 IconName::Ellipsis => "icons/ellipsis.svg",
209 IconName::Envelope => "icons/feedback.svg",
210 IconName::Escape => "icons/escape.svg",
211 IconName::ExclamationTriangle => "icons/warning.svg",
212 IconName::Exit => "icons/exit.svg",
213 IconName::ExternalLink => "icons/external_link.svg",
214 IconName::File => "icons/file.svg",
215 IconName::FileDoc => "icons/file_icons/book.svg",
216 IconName::FileGeneric => "icons/file_icons/file.svg",
217 IconName::FileGit => "icons/file_icons/git.svg",
218 IconName::FileLock => "icons/file_icons/lock.svg",
219 IconName::FileRust => "icons/file_icons/rust.svg",
220 IconName::FileToml => "icons/file_icons/toml.svg",
221 IconName::FileTree => "icons/project.svg",
222 IconName::Filter => "icons/filter.svg",
223 IconName::Folder => "icons/file_icons/folder.svg",
224 IconName::FolderOpen => "icons/file_icons/folder_open.svg",
225 IconName::FolderX => "icons/stop_sharing.svg",
226 IconName::Github => "icons/github.svg",
227 IconName::Hash => "icons/hash.svg",
228 IconName::InlayHint => "icons/inlay_hint.svg",
229 IconName::Link => "icons/link.svg",
230 IconName::MagicWand => "icons/magic_wand.svg",
231 IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
232 IconName::MailOpen => "icons/mail_open.svg",
233 IconName::Maximize => "icons/maximize.svg",
234 IconName::Menu => "icons/menu.svg",
235 IconName::MessageBubbles => "icons/conversations.svg",
236 IconName::Mic => "icons/mic.svg",
237 IconName::MicMute => "icons/mic_mute.svg",
238 IconName::Minimize => "icons/minimize.svg",
239 IconName::Option => "icons/option.svg",
240 IconName::PageDown => "icons/page_down.svg",
241 IconName::PageUp => "icons/page_up.svg",
242 IconName::Person => "icons/person.svg",
243 IconName::Pencil => "icons/pencil.svg",
244 IconName::Play => "icons/play.svg",
245 IconName::Plus => "icons/plus.svg",
246 IconName::Public => "icons/public.svg",
247 IconName::Quote => "icons/quote.svg",
248 IconName::Regex => "icons/regex.svg",
249 IconName::Replace => "icons/replace.svg",
250 IconName::ReplaceAll => "icons/replace_all.svg",
251 IconName::ReplaceNext => "icons/replace_next.svg",
252 IconName::Return => "icons/return.svg",
253 IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
254 IconName::Settings => "icons/file_icons/settings.svg",
255 IconName::Sliders => "icons/sliders.svg",
256 IconName::Screen => "icons/desktop.svg",
257 IconName::SelectAll => "icons/select_all.svg",
258 IconName::Server => "icons/server.svg",
259 IconName::Shift => "icons/shift.svg",
260 IconName::Snip => "icons/snip.svg",
261 IconName::Space => "icons/space.svg",
262 IconName::Split => "icons/split.svg",
263 IconName::Spinner => "icons/spinner.svg",
264 IconName::Tab => "icons/tab.svg",
265 IconName::Terminal => "icons/terminal.svg",
266 IconName::Trash => "icons/trash.svg",
267 IconName::Update => "icons/update.svg",
268 IconName::WholeWord => "icons/word_search.svg",
269 IconName::XCircle => "icons/error.svg",
270 IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
271 IconName::ZedAssistant => "icons/zed_assistant.svg",
272 IconName::PullRequest => "icons/pull_request.svg",
273 }
274 }
275}
276
277#[derive(IntoElement)]
278pub struct Icon {
279 path: SharedString,
280 color: Color,
281 size: Rems,
282 transformation: Transformation,
283}
284
285impl Icon {
286 pub fn new(icon: IconName) -> Self {
287 Self {
288 path: icon.path().into(),
289 color: Color::default(),
290 size: IconSize::default().rems(),
291 transformation: Transformation::default(),
292 }
293 }
294
295 pub fn from_path(path: impl Into<SharedString>) -> Self {
296 Self {
297 path: path.into(),
298 color: Color::default(),
299 size: IconSize::default().rems(),
300 transformation: Transformation::default(),
301 }
302 }
303
304 pub fn color(mut self, color: Color) -> Self {
305 self.color = color;
306 self
307 }
308
309 pub fn size(mut self, size: IconSize) -> Self {
310 self.size = size.rems();
311 self
312 }
313
314 /// Sets a custom size for the icon, in [`Rems`].
315 ///
316 /// Not to be exposed outside of the `ui` crate.
317 pub(crate) fn custom_size(mut self, size: Rems) -> Self {
318 self.size = size;
319 self
320 }
321
322 pub fn transform(mut self, transformation: Transformation) -> Self {
323 self.transformation = transformation;
324 self
325 }
326}
327
328impl RenderOnce for Icon {
329 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
330 svg()
331 .with_transformation(self.transformation)
332 .size(self.size)
333 .flex_none()
334 .path(self.path)
335 .text_color(self.color.color(cx))
336 }
337}
338
339#[derive(IntoElement)]
340pub struct IconWithIndicator {
341 icon: Icon,
342 indicator: Option<Indicator>,
343 indicator_border_color: Option<Hsla>,
344}
345
346impl IconWithIndicator {
347 pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
348 Self {
349 icon,
350 indicator,
351 indicator_border_color: None,
352 }
353 }
354
355 pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
356 self.indicator = indicator;
357 self
358 }
359
360 pub fn indicator_color(mut self, color: Color) -> Self {
361 if let Some(indicator) = self.indicator.as_mut() {
362 indicator.color = color;
363 }
364 self
365 }
366
367 pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
368 self.indicator_border_color = color;
369 self
370 }
371}
372
373impl RenderOnce for IconWithIndicator {
374 fn render(self, cx: &mut WindowContext) -> impl IntoElement {
375 let indicator_border_color = self
376 .indicator_border_color
377 .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
378
379 div()
380 .relative()
381 .child(self.icon)
382 .when_some(self.indicator, |this, indicator| {
383 this.child(
384 div()
385 .absolute()
386 .w_2()
387 .h_2()
388 .border()
389 .border_color(indicator_border_color)
390 .rounded_full()
391 .neg_bottom_0p5()
392 .neg_right_1()
393 .child(indicator),
394 )
395 })
396 }
397}