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