icon.rs

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