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