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