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