icon.rs

  1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  2use serde::{Deserialize, Serialize};
  3use strum::{EnumIter, EnumString, IntoStaticStr};
  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    /// Returns the individual components of the square that contains this [`IconSize`].
 80    ///
 81    /// The returned tuple contains:
 82    ///   1. The length of one side of the square
 83    ///   2. The padding of one side of the square
 84    pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
 85        let icon_size = self.rems() * cx.rem_size();
 86        let padding = match self {
 87            IconSize::Indicator => Spacing::None.px(cx),
 88            IconSize::XSmall => Spacing::XSmall.px(cx),
 89            IconSize::Small => Spacing::XSmall.px(cx),
 90            IconSize::Medium => Spacing::XSmall.px(cx),
 91        };
 92
 93        (icon_size, padding)
 94    }
 95
 96    /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
 97    pub fn square(&self, cx: &mut WindowContext) -> Pixels {
 98        let (icon_size, padding) = self.square_components(cx);
 99
100        icon_size + padding * 2.
101    }
102}
103
104#[derive(
105    Debug, PartialEq, Eq, Copy, Clone, EnumIter, EnumString, IntoStaticStr, Serialize, Deserialize,
106)]
107pub enum IconName {
108    Ai,
109    AiAnthropic,
110    AiAnthropicHosted,
111    AiOpenAi,
112    AiGoogle,
113    AiOllama,
114    AiZed,
115    ArrowCircle,
116    ArrowDown,
117    ArrowDownFromLine,
118    ArrowLeft,
119    ArrowRight,
120    ArrowUp,
121    ArrowUpFromLine,
122    ArrowUpRight,
123    AtSign,
124    AudioOff,
125    AudioOn,
126    Backspace,
127    Bell,
128    BellDot,
129    BellOff,
130    BellRing,
131    Bolt,
132    Book,
133    BookCopy,
134    BookPlus,
135    CaseSensitive,
136    Check,
137    ChevronDown,
138    /// This chevron indicates a popover menu.
139    ChevronDownSmall,
140    ChevronLeft,
141    ChevronRight,
142    ChevronUp,
143    ChevronUpDown,
144    Close,
145    Code,
146    Collab,
147    Command,
148    Context,
149    Control,
150    Copilot,
151    CopilotDisabled,
152    CopilotError,
153    CopilotInit,
154    Copy,
155    CountdownTimer,
156    Dash,
157    DatabaseZap,
158    Delete,
159    Disconnected,
160    Download,
161    Ellipsis,
162    EllipsisVertical,
163    Envelope,
164    Escape,
165    ExclamationTriangle,
166    Exit,
167    ExpandVertical,
168    ExternalLink,
169    Eye,
170    File,
171    FileDoc,
172    FileGeneric,
173    FileGit,
174    FileLock,
175    FileRust,
176    FileToml,
177    FileTree,
178    FileText,
179    FileCode,
180    Filter,
181    Folder,
182    FolderOpen,
183    FolderX,
184    Font,
185    FontSize,
186    FontWeight,
187    Github,
188    GenericMinimize,
189    GenericMaximize,
190    GenericClose,
191    GenericRestore,
192    Hash,
193    HistoryRerun,
194    Indicator,
195    IndicatorX,
196    InlayHint,
197    Library,
198    LineHeight,
199    Link,
200    ListTree,
201    MagnifyingGlass,
202    MailOpen,
203    Maximize,
204    Menu,
205    MessageBubbles,
206    Mic,
207    MicMute,
208    Microscope,
209    Minimize,
210    Option,
211    PageDown,
212    PageUp,
213    Pencil,
214    Person,
215    Pin,
216    Play,
217    Plus,
218    PocketKnife,
219    Public,
220    PullRequest,
221    Quote,
222    Regex,
223    ReplNeutral,
224    Replace,
225    ReplaceAll,
226    ReplaceNext,
227    ReplyArrowRight,
228    Rerun,
229    Return,
230    Reveal,
231    Route,
232    RotateCcw,
233    RotateCw,
234    Save,
235    Screen,
236    SearchSelection,
237    SearchCode,
238    SelectAll,
239    Server,
240    Settings,
241    Shift,
242    Slash,
243    SlashSquare,
244    Sliders,
245    SlidersAlt,
246    Snip,
247    Space,
248    Sparkle,
249    SparkleAlt,
250    SparkleFilled,
251    Spinner,
252    Split,
253    Star,
254    StarFilled,
255    Stop,
256    Strikethrough,
257    Supermaven,
258    SupermavenDisabled,
259    SupermavenError,
260    SupermavenInit,
261    Tab,
262    Terminal,
263    TextCursor,
264    TextSelect,
265    Trash,
266    TriangleRight,
267    Undo,
268    Unpin,
269    Update,
270    WholeWord,
271    XCircle,
272    ZedAssistant,
273    ZedAssistantFilled,
274    ZedXCopilot,
275    Visible,
276}
277
278impl IconName {
279    pub fn path(self) -> &'static str {
280        match self {
281            IconName::Ai => "icons/ai.svg",
282            IconName::AiAnthropic => "icons/ai_anthropic.svg",
283            IconName::AiAnthropicHosted => "icons/ai_anthropic_hosted.svg",
284            IconName::AiOpenAi => "icons/ai_open_ai.svg",
285            IconName::AiGoogle => "icons/ai_google.svg",
286            IconName::AiOllama => "icons/ai_ollama.svg",
287            IconName::AiZed => "icons/ai_zed.svg",
288            IconName::ArrowCircle => "icons/arrow_circle.svg",
289            IconName::ArrowDown => "icons/arrow_down.svg",
290            IconName::ArrowDownFromLine => "icons/arrow_down_from_line.svg",
291            IconName::ArrowLeft => "icons/arrow_left.svg",
292            IconName::ArrowRight => "icons/arrow_right.svg",
293            IconName::ArrowUp => "icons/arrow_up.svg",
294            IconName::ArrowUpFromLine => "icons/arrow_up_from_line.svg",
295            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
296            IconName::AtSign => "icons/at_sign.svg",
297            IconName::AudioOff => "icons/speaker_off.svg",
298            IconName::AudioOn => "icons/speaker_loud.svg",
299            IconName::Backspace => "icons/backspace.svg",
300            IconName::Bell => "icons/bell.svg",
301            IconName::BellDot => "icons/bell_dot.svg",
302            IconName::BellOff => "icons/bell_off.svg",
303            IconName::BellRing => "icons/bell_ring.svg",
304            IconName::Bolt => "icons/bolt.svg",
305            IconName::Book => "icons/book.svg",
306            IconName::BookCopy => "icons/book_copy.svg",
307            IconName::BookPlus => "icons/book_plus.svg",
308            IconName::CaseSensitive => "icons/case_insensitive.svg",
309            IconName::Check => "icons/check.svg",
310            IconName::ChevronDown => "icons/chevron_down.svg",
311            IconName::ChevronDownSmall => "icons/chevron_down_small.svg",
312            IconName::ChevronLeft => "icons/chevron_left.svg",
313            IconName::ChevronRight => "icons/chevron_right.svg",
314            IconName::ChevronUp => "icons/chevron_up.svg",
315            IconName::ChevronUpDown => "icons/chevron_up_down.svg",
316            IconName::Close => "icons/x.svg",
317            IconName::Code => "icons/code.svg",
318            IconName::Collab => "icons/user_group_16.svg",
319            IconName::Command => "icons/command.svg",
320            IconName::Context => "icons/context.svg",
321            IconName::Control => "icons/control.svg",
322            IconName::Copilot => "icons/copilot.svg",
323            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
324            IconName::CopilotError => "icons/copilot_error.svg",
325            IconName::CopilotInit => "icons/copilot_init.svg",
326            IconName::Copy => "icons/copy.svg",
327            IconName::CountdownTimer => "icons/countdown_timer.svg",
328            IconName::Dash => "icons/dash.svg",
329            IconName::DatabaseZap => "icons/database_zap.svg",
330            IconName::Delete => "icons/delete.svg",
331            IconName::Disconnected => "icons/disconnected.svg",
332            IconName::Download => "icons/download.svg",
333            IconName::Ellipsis => "icons/ellipsis.svg",
334            IconName::EllipsisVertical => "icons/ellipsis_vertical.svg",
335            IconName::Envelope => "icons/feedback.svg",
336            IconName::Escape => "icons/escape.svg",
337            IconName::ExclamationTriangle => "icons/warning.svg",
338            IconName::Exit => "icons/exit.svg",
339            IconName::ExpandVertical => "icons/expand_vertical.svg",
340            IconName::ExternalLink => "icons/external_link.svg",
341            IconName::Eye => "icons/eye.svg",
342            IconName::File => "icons/file.svg",
343            IconName::FileDoc => "icons/file_icons/book.svg",
344            IconName::FileGeneric => "icons/file_icons/file.svg",
345            IconName::FileGit => "icons/file_icons/git.svg",
346            IconName::FileLock => "icons/file_icons/lock.svg",
347            IconName::FileRust => "icons/file_icons/rust.svg",
348            IconName::FileToml => "icons/file_icons/toml.svg",
349            IconName::FileTree => "icons/project.svg",
350            IconName::FileCode => "icons/file_code.svg",
351            IconName::FileText => "icons/file_text.svg",
352            IconName::Filter => "icons/filter.svg",
353            IconName::Folder => "icons/file_icons/folder.svg",
354            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
355            IconName::FolderX => "icons/stop_sharing.svg",
356            IconName::Font => "icons/font.svg",
357            IconName::FontSize => "icons/font_size.svg",
358            IconName::FontWeight => "icons/font_weight.svg",
359            IconName::Github => "icons/github.svg",
360            IconName::GenericMinimize => "icons/generic_minimize.svg",
361            IconName::GenericMaximize => "icons/generic_maximize.svg",
362            IconName::GenericClose => "icons/generic_close.svg",
363            IconName::GenericRestore => "icons/generic_restore.svg",
364            IconName::Hash => "icons/hash.svg",
365            IconName::HistoryRerun => "icons/history_rerun.svg",
366            IconName::Indicator => "icons/indicator.svg",
367            IconName::IndicatorX => "icons/indicator_x.svg",
368            IconName::InlayHint => "icons/inlay_hint.svg",
369            IconName::Library => "icons/library.svg",
370            IconName::LineHeight => "icons/line_height.svg",
371            IconName::Link => "icons/link.svg",
372            IconName::ListTree => "icons/list_tree.svg",
373            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
374            IconName::MailOpen => "icons/mail_open.svg",
375            IconName::Maximize => "icons/maximize.svg",
376            IconName::Menu => "icons/menu.svg",
377            IconName::MessageBubbles => "icons/conversations.svg",
378            IconName::Mic => "icons/mic.svg",
379            IconName::MicMute => "icons/mic_mute.svg",
380            IconName::Microscope => "icons/microscope.svg",
381            IconName::Minimize => "icons/minimize.svg",
382            IconName::Option => "icons/option.svg",
383            IconName::PageDown => "icons/page_down.svg",
384            IconName::PageUp => "icons/page_up.svg",
385            IconName::Pencil => "icons/pencil.svg",
386            IconName::Person => "icons/person.svg",
387            IconName::Pin => "icons/pin.svg",
388            IconName::Play => "icons/play.svg",
389            IconName::Plus => "icons/plus.svg",
390            IconName::PocketKnife => "icons/pocket_knife.svg",
391            IconName::Public => "icons/public.svg",
392            IconName::PullRequest => "icons/pull_request.svg",
393            IconName::Quote => "icons/quote.svg",
394            IconName::Regex => "icons/regex.svg",
395            IconName::ReplNeutral => "icons/repl_neutral.svg",
396            IconName::Replace => "icons/replace.svg",
397            IconName::ReplaceAll => "icons/replace_all.svg",
398            IconName::ReplaceNext => "icons/replace_next.svg",
399            IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
400            IconName::Rerun => "icons/rerun.svg",
401            IconName::Return => "icons/return.svg",
402            IconName::Reveal => "icons/reveal.svg",
403            IconName::RotateCcw => "icons/rotate_ccw.svg",
404            IconName::RotateCw => "icons/rotate_cw.svg",
405            IconName::Route => "icons/route.svg",
406            IconName::Save => "icons/save.svg",
407            IconName::Screen => "icons/desktop.svg",
408            IconName::SearchSelection => "icons/search_selection.svg",
409            IconName::SearchCode => "icons/search_code.svg",
410            IconName::SelectAll => "icons/select_all.svg",
411            IconName::Server => "icons/server.svg",
412            IconName::Settings => "icons/file_icons/settings.svg",
413            IconName::Shift => "icons/shift.svg",
414            IconName::Slash => "icons/slash.svg",
415            IconName::SlashSquare => "icons/slash_square.svg",
416            IconName::Sliders => "icons/sliders.svg",
417            IconName::SlidersAlt => "icons/sliders-alt.svg",
418            IconName::Snip => "icons/snip.svg",
419            IconName::Space => "icons/space.svg",
420            IconName::Sparkle => "icons/sparkle.svg",
421            IconName::SparkleAlt => "icons/sparkle_alt.svg",
422            IconName::SparkleFilled => "icons/sparkle_filled.svg",
423            IconName::Spinner => "icons/spinner.svg",
424            IconName::Split => "icons/split.svg",
425            IconName::Star => "icons/star.svg",
426            IconName::StarFilled => "icons/star_filled.svg",
427            IconName::Stop => "icons/stop.svg",
428            IconName::Strikethrough => "icons/strikethrough.svg",
429            IconName::Supermaven => "icons/supermaven.svg",
430            IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
431            IconName::SupermavenError => "icons/supermaven_error.svg",
432            IconName::SupermavenInit => "icons/supermaven_init.svg",
433            IconName::Tab => "icons/tab.svg",
434            IconName::Terminal => "icons/terminal.svg",
435            IconName::TextCursor => "icons/text-cursor.svg",
436            IconName::TextSelect => "icons/text_select.svg",
437            IconName::Trash => "icons/trash.svg",
438            IconName::TriangleRight => "icons/triangle_right.svg",
439            IconName::Unpin => "icons/unpin.svg",
440            IconName::Update => "icons/update.svg",
441            IconName::Undo => "icons/undo.svg",
442            IconName::WholeWord => "icons/word_search.svg",
443            IconName::XCircle => "icons/error.svg",
444            IconName::ZedAssistant => "icons/zed_assistant.svg",
445            IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
446            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
447            IconName::Visible => "icons/visible.svg",
448        }
449    }
450}
451
452#[derive(IntoElement)]
453pub struct Icon {
454    path: SharedString,
455    color: Color,
456    size: Rems,
457    transformation: Transformation,
458}
459
460impl Icon {
461    pub fn new(icon: IconName) -> Self {
462        Self {
463            path: icon.path().into(),
464            color: Color::default(),
465            size: IconSize::default().rems(),
466            transformation: Transformation::default(),
467        }
468    }
469
470    pub fn from_path(path: impl Into<SharedString>) -> Self {
471        Self {
472            path: path.into(),
473            color: Color::default(),
474            size: IconSize::default().rems(),
475            transformation: Transformation::default(),
476        }
477    }
478
479    pub fn color(mut self, color: Color) -> Self {
480        self.color = color;
481        self
482    }
483
484    pub fn size(mut self, size: IconSize) -> Self {
485        self.size = size.rems();
486        self
487    }
488
489    /// Sets a custom size for the icon, in [`Rems`].
490    ///
491    /// Not to be exposed outside of the `ui` crate.
492    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
493        self.size = size;
494        self
495    }
496
497    pub fn transform(mut self, transformation: Transformation) -> Self {
498        self.transformation = transformation;
499        self
500    }
501}
502
503impl RenderOnce for Icon {
504    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
505        svg()
506            .with_transformation(self.transformation)
507            .size(self.size)
508            .flex_none()
509            .path(self.path)
510            .text_color(self.color.color(cx))
511    }
512}
513
514#[derive(IntoElement)]
515pub struct DecoratedIcon {
516    icon: Icon,
517    decoration: IconDecoration,
518    decoration_color: Color,
519    parent_background: Option<Hsla>,
520}
521
522impl DecoratedIcon {
523    pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
524        Self {
525            icon,
526            decoration,
527            decoration_color: Color::Default,
528            parent_background: None,
529        }
530    }
531
532    pub fn decoration_color(mut self, color: Color) -> Self {
533        self.decoration_color = color;
534        self
535    }
536
537    pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
538        self.parent_background = background;
539        self
540    }
541}
542
543impl RenderOnce for DecoratedIcon {
544    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
545        let background = self
546            .parent_background
547            .unwrap_or(cx.theme().colors().background);
548
549        let size = self.icon.size;
550
551        let decoration_icon = match self.decoration {
552            IconDecoration::Strikethrough => IconName::Strikethrough,
553            IconDecoration::IndicatorDot => IconName::Indicator,
554            IconDecoration::X => IconName::IndicatorX,
555        };
556
557        let decoration_svg = |icon: IconName| {
558            svg()
559                .absolute()
560                .top_0()
561                .left_0()
562                .path(icon.path())
563                .size(size)
564                .flex_none()
565                .text_color(self.decoration_color.color(cx))
566        };
567
568        let decoration_knockout = |icon: IconName| {
569            svg()
570                .absolute()
571                .top(-rems_from_px(2.))
572                .left(-rems_from_px(3.))
573                .path(icon.path())
574                .size(size + rems_from_px(2.))
575                .flex_none()
576                .text_color(background)
577        };
578
579        div()
580            .relative()
581            .size(self.icon.size)
582            .child(self.icon)
583            .child(decoration_knockout(decoration_icon))
584            .child(decoration_svg(decoration_icon))
585    }
586}
587
588#[derive(IntoElement)]
589pub struct IconWithIndicator {
590    icon: Icon,
591    indicator: Option<Indicator>,
592    indicator_border_color: Option<Hsla>,
593}
594
595impl IconWithIndicator {
596    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
597        Self {
598            icon,
599            indicator,
600            indicator_border_color: None,
601        }
602    }
603
604    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
605        self.indicator = indicator;
606        self
607    }
608
609    pub fn indicator_color(mut self, color: Color) -> Self {
610        if let Some(indicator) = self.indicator.as_mut() {
611            indicator.color = color;
612        }
613        self
614    }
615
616    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
617        self.indicator_border_color = color;
618        self
619    }
620}
621
622impl RenderOnce for IconWithIndicator {
623    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
624        let indicator_border_color = self
625            .indicator_border_color
626            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
627
628        div()
629            .relative()
630            .child(self.icon)
631            .when_some(self.indicator, |this, indicator| {
632                this.child(
633                    div()
634                        .absolute()
635                        .w_2()
636                        .h_2()
637                        .border_1()
638                        .border_color(indicator_border_color)
639                        .rounded_full()
640                        .bottom_neg_0p5()
641                        .right_neg_1()
642                        .child(indicator),
643                )
644            })
645    }
646}