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