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