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