icon.rs

  1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  2use serde::{Deserialize, Serialize};
  3use strum::EnumIter;
  4
  5use crate::{prelude::*, Indicator};
  6
  7#[derive(IntoElement)]
  8pub enum AnyIcon {
  9    Icon(Icon),
 10    AnimatedIcon(AnimationElement<Icon>),
 11}
 12
 13impl AnyIcon {
 14    /// Returns a new [`AnyIcon`] after applying the given mapping function
 15    /// to the contained [`Icon`].
 16    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
 17        match self {
 18            Self::Icon(icon) => Self::Icon(f(icon)),
 19            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
 20        }
 21    }
 22}
 23
 24impl From<Icon> for AnyIcon {
 25    fn from(value: Icon) -> Self {
 26        Self::Icon(value)
 27    }
 28}
 29
 30impl From<AnimationElement<Icon>> for AnyIcon {
 31    fn from(value: AnimationElement<Icon>) -> Self {
 32        Self::AnimatedIcon(value)
 33    }
 34}
 35
 36impl RenderOnce for AnyIcon {
 37    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 38        match self {
 39            Self::Icon(icon) => icon.into_any_element(),
 40            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
 41        }
 42    }
 43}
 44
 45/// The decoration for an icon.
 46///
 47/// For example, this can show an indicator, an "x",
 48/// or a diagonal strkethrough to indicate something is disabled.
 49#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 50pub enum IconDecoration {
 51    Strikethrough,
 52    IndicatorDot,
 53    X,
 54}
 55
 56#[derive(Default, PartialEq, Copy, Clone)]
 57pub enum IconSize {
 58    /// 10px
 59    Indicator,
 60    /// 12px
 61    XSmall,
 62    /// 14px
 63    Small,
 64    #[default]
 65    /// 16px
 66    Medium,
 67}
 68
 69impl IconSize {
 70    pub fn rems(self) -> Rems {
 71        match self {
 72            IconSize::Indicator => rems_from_px(10.),
 73            IconSize::XSmall => rems_from_px(12.),
 74            IconSize::Small => rems_from_px(14.),
 75            IconSize::Medium => rems_from_px(16.),
 76        }
 77    }
 78}
 79
 80#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
 81pub enum IconName {
 82    Ai,
 83    ArrowCircle,
 84    ArrowDown,
 85    ArrowDownFromLine,
 86    ArrowLeft,
 87    ArrowRight,
 88    ArrowUp,
 89    ArrowUpFromLine,
 90    ArrowUpRight,
 91    AtSign,
 92    AudioOff,
 93    AudioOn,
 94    Backspace,
 95    Bell,
 96    BellDot,
 97    BellOff,
 98    BellRing,
 99    Bolt,
100    Book,
101    BookCopy,
102    BookPlus,
103    CaseSensitive,
104    Check,
105    ChevronDown,
106    ChevronLeft,
107    ChevronRight,
108    ChevronUp,
109    ChevronUpDown,
110    Close,
111    Code,
112    Collab,
113    Command,
114    Context,
115    Control,
116    Copilot,
117    CopilotDisabled,
118    CopilotError,
119    CopilotInit,
120    Copy,
121    CountdownTimer,
122    Dash,
123    Delete,
124    Disconnected,
125    Download,
126    Ellipsis,
127    Envelope,
128    Escape,
129    ExclamationTriangle,
130    Exit,
131    ExpandVertical,
132    ExternalLink,
133    File,
134    FileDoc,
135    FileGeneric,
136    FileGit,
137    FileLock,
138    FileRust,
139    FileToml,
140    FileTree,
141    Filter,
142    Folder,
143    FolderOpen,
144    FolderX,
145    Font,
146    FontSize,
147    FontWeight,
148    Github,
149    Hash,
150    HistoryRerun,
151    Indicator,
152    IndicatorX,
153    InlayHint,
154    Library,
155    LineHeight,
156    Link,
157    ListTree,
158    MagicWand,
159    MagnifyingGlass,
160    MailOpen,
161    Maximize,
162    Menu,
163    MessageBubbles,
164    Mic,
165    MicMute,
166    Minimize,
167    Option,
168    PageDown,
169    PageUp,
170    Pencil,
171    Person,
172    Play,
173    Plus,
174    Public,
175    PullRequest,
176    Quote,
177    Regex,
178    Replace,
179    ReplaceAll,
180    ReplaceNext,
181    ReplyArrowRight,
182    Rerun,
183    Return,
184    Reveal,
185    RotateCcw,
186    RotateCw,
187    Save,
188    Screen,
189    SearchSelection,
190    SelectAll,
191    Server,
192    Settings,
193    Shift,
194    Sliders,
195    Snip,
196    Space,
197    Sparkle,
198    SparkleFilled,
199    Spinner,
200    Split,
201    Star,
202    StarFilled,
203    Stop,
204    Strikethrough,
205    Supermaven,
206    SupermavenDisabled,
207    SupermavenError,
208    SupermavenInit,
209    Tab,
210    Terminal,
211    TextCursor,
212    Trash,
213    TriangleRight,
214    Update,
215    WholeWord,
216    XCircle,
217    ZedAssistant,
218    ZedAssistantFilled,
219    ZedXCopilot,
220    Visible,
221}
222
223impl IconName {
224    pub fn path(self) -> &'static str {
225        match self {
226            IconName::Ai => "icons/ai.svg",
227            IconName::ArrowCircle => "icons/arrow_circle.svg",
228            IconName::ArrowDown => "icons/arrow_down.svg",
229            IconName::ArrowDownFromLine => "icons/arrow_down_from_line.svg",
230            IconName::ArrowLeft => "icons/arrow_left.svg",
231            IconName::ArrowRight => "icons/arrow_right.svg",
232            IconName::ArrowUp => "icons/arrow_up.svg",
233            IconName::ArrowUpFromLine => "icons/arrow_up_from_line.svg",
234            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
235            IconName::AtSign => "icons/at_sign.svg",
236            IconName::AudioOff => "icons/speaker_off.svg",
237            IconName::AudioOn => "icons/speaker_loud.svg",
238            IconName::Backspace => "icons/backspace.svg",
239            IconName::Bell => "icons/bell.svg",
240            IconName::BellDot => "icons/bell_dot.svg",
241            IconName::BellOff => "icons/bell_off.svg",
242            IconName::BellRing => "icons/bell_ring.svg",
243            IconName::Bolt => "icons/bolt.svg",
244            IconName::Book => "icons/book.svg",
245            IconName::BookCopy => "icons/book_copy.svg",
246            IconName::BookPlus => "icons/book_plus.svg",
247            IconName::CaseSensitive => "icons/case_insensitive.svg",
248            IconName::Check => "icons/check.svg",
249            IconName::ChevronDown => "icons/chevron_down.svg",
250            IconName::ChevronLeft => "icons/chevron_left.svg",
251            IconName::ChevronRight => "icons/chevron_right.svg",
252            IconName::ChevronUp => "icons/chevron_up.svg",
253            IconName::ChevronUpDown => "icons/chevron_up_down.svg",
254            IconName::Close => "icons/x.svg",
255            IconName::Code => "icons/code.svg",
256            IconName::Collab => "icons/user_group_16.svg",
257            IconName::Command => "icons/command.svg",
258            IconName::Context => "icons/context.svg",
259            IconName::Control => "icons/control.svg",
260            IconName::Copilot => "icons/copilot.svg",
261            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
262            IconName::CopilotError => "icons/copilot_error.svg",
263            IconName::CopilotInit => "icons/copilot_init.svg",
264            IconName::Copy => "icons/copy.svg",
265            IconName::CountdownTimer => "icons/countdown_timer.svg",
266            IconName::Dash => "icons/dash.svg",
267            IconName::Delete => "icons/delete.svg",
268            IconName::Disconnected => "icons/disconnected.svg",
269            IconName::Download => "icons/download.svg",
270            IconName::Ellipsis => "icons/ellipsis.svg",
271            IconName::Envelope => "icons/feedback.svg",
272            IconName::Escape => "icons/escape.svg",
273            IconName::ExclamationTriangle => "icons/warning.svg",
274            IconName::Exit => "icons/exit.svg",
275            IconName::ExpandVertical => "icons/expand_vertical.svg",
276            IconName::ExternalLink => "icons/external_link.svg",
277            IconName::File => "icons/file.svg",
278            IconName::FileDoc => "icons/file_icons/book.svg",
279            IconName::FileGeneric => "icons/file_icons/file.svg",
280            IconName::FileGit => "icons/file_icons/git.svg",
281            IconName::FileLock => "icons/file_icons/lock.svg",
282            IconName::FileRust => "icons/file_icons/rust.svg",
283            IconName::FileToml => "icons/file_icons/toml.svg",
284            IconName::FileTree => "icons/project.svg",
285            IconName::Filter => "icons/filter.svg",
286            IconName::Folder => "icons/file_icons/folder.svg",
287            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
288            IconName::FolderX => "icons/stop_sharing.svg",
289            IconName::Font => "icons/font.svg",
290            IconName::FontSize => "icons/font_size.svg",
291            IconName::FontWeight => "icons/font_weight.svg",
292            IconName::Github => "icons/github.svg",
293            IconName::Hash => "icons/hash.svg",
294            IconName::HistoryRerun => "icons/history_rerun.svg",
295            IconName::Indicator => "icons/indicator.svg",
296            IconName::IndicatorX => "icons/indicator_x.svg",
297            IconName::InlayHint => "icons/inlay_hint.svg",
298            IconName::Library => "icons/library.svg",
299            IconName::LineHeight => "icons/line_height.svg",
300            IconName::Link => "icons/link.svg",
301            IconName::ListTree => "icons/list_tree.svg",
302            IconName::MagicWand => "icons/magic_wand.svg",
303            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
304            IconName::MailOpen => "icons/mail_open.svg",
305            IconName::Maximize => "icons/maximize.svg",
306            IconName::Menu => "icons/menu.svg",
307            IconName::MessageBubbles => "icons/conversations.svg",
308            IconName::Mic => "icons/mic.svg",
309            IconName::MicMute => "icons/mic_mute.svg",
310            IconName::Minimize => "icons/minimize.svg",
311            IconName::Option => "icons/option.svg",
312            IconName::PageDown => "icons/page_down.svg",
313            IconName::PageUp => "icons/page_up.svg",
314            IconName::Pencil => "icons/pencil.svg",
315            IconName::Person => "icons/person.svg",
316            IconName::Play => "icons/play.svg",
317            IconName::Plus => "icons/plus.svg",
318            IconName::Public => "icons/public.svg",
319            IconName::PullRequest => "icons/pull_request.svg",
320            IconName::Quote => "icons/quote.svg",
321            IconName::Regex => "icons/regex.svg",
322            IconName::Replace => "icons/replace.svg",
323            IconName::ReplaceAll => "icons/replace_all.svg",
324            IconName::ReplaceNext => "icons/replace_next.svg",
325            IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
326            IconName::Rerun => "icons/rerun.svg",
327            IconName::Return => "icons/return.svg",
328            IconName::Reveal => "icons/reveal.svg",
329            IconName::RotateCcw => "icons/rotate_ccw.svg",
330            IconName::RotateCw => "icons/rotate_cw.svg",
331            IconName::Save => "icons/save.svg",
332            IconName::Screen => "icons/desktop.svg",
333            IconName::SearchSelection => "icons/search_selection.svg",
334            IconName::SelectAll => "icons/select_all.svg",
335            IconName::Server => "icons/server.svg",
336            IconName::Settings => "icons/file_icons/settings.svg",
337            IconName::Shift => "icons/shift.svg",
338            IconName::Sliders => "icons/sliders.svg",
339            IconName::Snip => "icons/snip.svg",
340            IconName::Space => "icons/space.svg",
341            IconName::Sparkle => "icons/sparkle.svg",
342            IconName::SparkleFilled => "icons/sparkle_filled.svg",
343            IconName::Spinner => "icons/spinner.svg",
344            IconName::Split => "icons/split.svg",
345            IconName::Star => "icons/star.svg",
346            IconName::StarFilled => "icons/star_filled.svg",
347            IconName::Stop => "icons/stop.svg",
348            IconName::Strikethrough => "icons/strikethrough.svg",
349            IconName::Supermaven => "icons/supermaven.svg",
350            IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
351            IconName::SupermavenError => "icons/supermaven_error.svg",
352            IconName::SupermavenInit => "icons/supermaven_init.svg",
353            IconName::Tab => "icons/tab.svg",
354            IconName::Terminal => "icons/terminal.svg",
355            IconName::TextCursor => "icons/text-cursor.svg",
356            IconName::Trash => "icons/trash.svg",
357            IconName::TriangleRight => "icons/triangle_right.svg",
358            IconName::Update => "icons/update.svg",
359            IconName::WholeWord => "icons/word_search.svg",
360            IconName::XCircle => "icons/error.svg",
361            IconName::ZedAssistant => "icons/zed_assistant.svg",
362            IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
363            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
364            IconName::Visible => "icons/visible.svg",
365        }
366    }
367}
368
369#[derive(IntoElement)]
370pub struct Icon {
371    path: SharedString,
372    color: Color,
373    size: Rems,
374    transformation: Transformation,
375}
376
377impl Icon {
378    pub fn new(icon: IconName) -> Self {
379        Self {
380            path: icon.path().into(),
381            color: Color::default(),
382            size: IconSize::default().rems(),
383            transformation: Transformation::default(),
384        }
385    }
386
387    pub fn from_path(path: impl Into<SharedString>) -> Self {
388        Self {
389            path: path.into(),
390            color: Color::default(),
391            size: IconSize::default().rems(),
392            transformation: Transformation::default(),
393        }
394    }
395
396    pub fn color(mut self, color: Color) -> Self {
397        self.color = color;
398        self
399    }
400
401    pub fn size(mut self, size: IconSize) -> Self {
402        self.size = size.rems();
403        self
404    }
405
406    /// Sets a custom size for the icon, in [`Rems`].
407    ///
408    /// Not to be exposed outside of the `ui` crate.
409    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
410        self.size = size;
411        self
412    }
413
414    pub fn transform(mut self, transformation: Transformation) -> Self {
415        self.transformation = transformation;
416        self
417    }
418}
419
420impl RenderOnce for Icon {
421    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
422        svg()
423            .with_transformation(self.transformation)
424            .size(self.size)
425            .flex_none()
426            .path(self.path)
427            .text_color(self.color.color(cx))
428    }
429}
430
431#[derive(IntoElement)]
432pub struct DecoratedIcon {
433    icon: Icon,
434    decoration: IconDecoration,
435    decoration_color: Color,
436    parent_background: Option<Hsla>,
437}
438
439impl DecoratedIcon {
440    pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
441        Self {
442            icon,
443            decoration,
444            decoration_color: Color::Default,
445            parent_background: None,
446        }
447    }
448
449    pub fn decoration_color(mut self, color: Color) -> Self {
450        self.decoration_color = color;
451        self
452    }
453
454    pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
455        self.parent_background = background;
456        self
457    }
458}
459
460impl RenderOnce for DecoratedIcon {
461    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
462        let background = self
463            .parent_background
464            .unwrap_or(cx.theme().colors().background);
465
466        let size = self.icon.size;
467
468        let decoration_icon = match self.decoration {
469            IconDecoration::Strikethrough => IconName::Strikethrough,
470            IconDecoration::IndicatorDot => IconName::Indicator,
471            IconDecoration::X => IconName::IndicatorX,
472        };
473
474        let decoration_svg = |icon: IconName| {
475            svg()
476                .absolute()
477                .top_0()
478                .left_0()
479                .path(icon.path())
480                .size(size)
481                .flex_none()
482                .text_color(self.decoration_color.color(cx))
483        };
484
485        let decoration_knockout = |icon: IconName| {
486            svg()
487                .absolute()
488                .top(-rems_from_px(2.))
489                .left(-rems_from_px(3.))
490                .path(icon.path())
491                .size(size + rems_from_px(2.))
492                .flex_none()
493                .text_color(background)
494        };
495
496        div()
497            .relative()
498            .size(self.icon.size)
499            .child(self.icon)
500            .child(decoration_knockout(decoration_icon))
501            .child(decoration_svg(decoration_icon))
502    }
503}
504
505#[derive(IntoElement)]
506pub struct IconWithIndicator {
507    icon: Icon,
508    indicator: Option<Indicator>,
509    indicator_border_color: Option<Hsla>,
510}
511
512impl IconWithIndicator {
513    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
514        Self {
515            icon,
516            indicator,
517            indicator_border_color: None,
518        }
519    }
520
521    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
522        self.indicator = indicator;
523        self
524    }
525
526    pub fn indicator_color(mut self, color: Color) -> Self {
527        if let Some(indicator) = self.indicator.as_mut() {
528            indicator.color = color;
529        }
530        self
531    }
532
533    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
534        self.indicator_border_color = color;
535        self
536    }
537}
538
539impl RenderOnce for IconWithIndicator {
540    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
541        let indicator_border_color = self
542            .indicator_border_color
543            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
544
545        div()
546            .relative()
547            .child(self.icon)
548            .when_some(self.indicator, |this, indicator| {
549                this.child(
550                    div()
551                        .absolute()
552                        .w_2()
553                        .h_2()
554                        .border_1()
555                        .border_color(indicator_border_color)
556                        .rounded_full()
557                        .bottom_neg_0p5()
558                        .right_neg_1()
559                        .child(indicator),
560                )
561            })
562    }
563}