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