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