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