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