icon.rs

  1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  2use strum::EnumIter;
  3
  4use crate::{prelude::*, Indicator};
  5
  6#[derive(IntoElement)]
  7pub enum AnyIcon {
  8    Icon(Icon),
  9    AnimatedIcon(AnimationElement<Icon>),
 10}
 11
 12impl AnyIcon {
 13    /// Returns a new [`AnyIcon`] after applying the given mapping function
 14    /// to the contained [`Icon`].
 15    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
 16        match self {
 17            Self::Icon(icon) => Self::Icon(f(icon)),
 18            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
 19        }
 20    }
 21}
 22
 23impl From<Icon> for AnyIcon {
 24    fn from(value: Icon) -> Self {
 25        Self::Icon(value)
 26    }
 27}
 28
 29impl From<AnimationElement<Icon>> for AnyIcon {
 30    fn from(value: AnimationElement<Icon>) -> Self {
 31        Self::AnimatedIcon(value)
 32    }
 33}
 34
 35impl RenderOnce for AnyIcon {
 36    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 37        match self {
 38            Self::Icon(icon) => icon.into_any_element(),
 39            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
 40        }
 41    }
 42}
 43
 44/// The decoration for an icon.
 45///
 46/// For example, this can show an indicator, an "x",
 47/// or a diagonal strkethrough to indicate something is disabled.
 48#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 49pub enum IconDecoration {
 50    Strikethrough,
 51    IndicatorDot,
 52    X,
 53}
 54
 55#[derive(Default, PartialEq, Copy, Clone)]
 56pub enum IconSize {
 57    Indicator,
 58    XSmall,
 59    Small,
 60    #[default]
 61    Medium,
 62}
 63
 64impl IconSize {
 65    pub fn rems(self) -> Rems {
 66        match self {
 67            IconSize::Indicator => rems_from_px(10.),
 68            IconSize::XSmall => rems_from_px(12.),
 69            IconSize::Small => rems_from_px(14.),
 70            IconSize::Medium => rems_from_px(16.),
 71        }
 72    }
 73}
 74
 75#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 76pub enum IconName {
 77    Ai,
 78    ArrowDown,
 79    ArrowLeft,
 80    ArrowRight,
 81    ArrowUp,
 82    ArrowUpRight,
 83    ArrowCircle,
 84    AtSign,
 85    AudioOff,
 86    AudioOn,
 87    Backspace,
 88    Bell,
 89    BellOff,
 90    BellRing,
 91    BellDot,
 92    Bolt,
 93    CaseSensitive,
 94    Check,
 95    ChevronDown,
 96    ChevronLeft,
 97    ChevronRight,
 98    ChevronUp,
 99    ExpandVertical,
100    Close,
101    Code,
102    Collab,
103    Command,
104    Control,
105    Copilot,
106    CopilotDisabled,
107    CopilotError,
108    CopilotInit,
109    Copy,
110    CountdownTimer,
111    Dash,
112    Delete,
113    Disconnected,
114    Ellipsis,
115    Envelope,
116    Escape,
117    ExclamationTriangle,
118    Exit,
119    ExternalLink,
120    File,
121    FileDoc,
122    FileGeneric,
123    FileGit,
124    FileLock,
125    FileRust,
126    FileToml,
127    FileTree,
128    Filter,
129    Folder,
130    FolderOpen,
131    FolderX,
132    Github,
133    Hash,
134    Indicator,
135    IndicatorX,
136    InlayHint,
137    Link,
138    MagicWand,
139    MagnifyingGlass,
140    MailOpen,
141    Maximize,
142    Menu,
143    MessageBubbles,
144    Mic,
145    MicMute,
146    Minimize,
147    Option,
148    PageDown,
149    PageUp,
150    Pencil,
151    Person,
152    Play,
153    Plus,
154    Public,
155    Quote,
156    Regex,
157    Replace,
158    ReplaceAll,
159    ReplaceNext,
160    Return,
161    ReplyArrowRight,
162    Settings,
163    Sliders,
164    Screen,
165    SelectAll,
166    Server,
167    Shift,
168    Snip,
169    Space,
170    Split,
171    Spinner,
172    Supermaven,
173    SupermavenDisabled,
174    SupermavenError,
175    SupermavenInit,
176    Strikethrough,
177    Tab,
178    Terminal,
179    Trash,
180    Update,
181    WholeWord,
182    XCircle,
183    ZedXCopilot,
184    ZedAssistant,
185    PullRequest,
186    HistoryRerun,
187}
188
189impl IconName {
190    pub fn path(self) -> &'static str {
191        match self {
192            IconName::Ai => "icons/ai.svg",
193            IconName::ArrowDown => "icons/arrow_down.svg",
194            IconName::ArrowLeft => "icons/arrow_left.svg",
195            IconName::ArrowRight => "icons/arrow_right.svg",
196            IconName::ArrowUp => "icons/arrow_up.svg",
197            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
198            IconName::ArrowCircle => "icons/arrow_circle.svg",
199            IconName::AtSign => "icons/at_sign.svg",
200            IconName::AudioOff => "icons/speaker_off.svg",
201            IconName::AudioOn => "icons/speaker_loud.svg",
202            IconName::Backspace => "icons/backspace.svg",
203            IconName::Bell => "icons/bell.svg",
204            IconName::BellOff => "icons/bell_off.svg",
205            IconName::BellRing => "icons/bell_ring.svg",
206            IconName::BellDot => "icons/bell_dot.svg",
207            IconName::Bolt => "icons/bolt.svg",
208            IconName::CaseSensitive => "icons/case_insensitive.svg",
209            IconName::Check => "icons/check.svg",
210            IconName::ChevronDown => "icons/chevron_down.svg",
211            IconName::ChevronLeft => "icons/chevron_left.svg",
212            IconName::ChevronRight => "icons/chevron_right.svg",
213            IconName::ChevronUp => "icons/chevron_up.svg",
214            IconName::ExpandVertical => "icons/expand_vertical.svg",
215            IconName::Close => "icons/x.svg",
216            IconName::Code => "icons/code.svg",
217            IconName::Collab => "icons/user_group_16.svg",
218            IconName::Command => "icons/command.svg",
219            IconName::Control => "icons/control.svg",
220            IconName::Copilot => "icons/copilot.svg",
221            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
222            IconName::CopilotError => "icons/copilot_error.svg",
223            IconName::CopilotInit => "icons/copilot_init.svg",
224            IconName::Copy => "icons/copy.svg",
225            IconName::CountdownTimer => "icons/countdown_timer.svg",
226            IconName::Dash => "icons/dash.svg",
227            IconName::Delete => "icons/delete.svg",
228            IconName::Disconnected => "icons/disconnected.svg",
229            IconName::Ellipsis => "icons/ellipsis.svg",
230            IconName::Envelope => "icons/feedback.svg",
231            IconName::Escape => "icons/escape.svg",
232            IconName::ExclamationTriangle => "icons/warning.svg",
233            IconName::Exit => "icons/exit.svg",
234            IconName::ExternalLink => "icons/external_link.svg",
235            IconName::File => "icons/file.svg",
236            IconName::FileDoc => "icons/file_icons/book.svg",
237            IconName::FileGeneric => "icons/file_icons/file.svg",
238            IconName::FileGit => "icons/file_icons/git.svg",
239            IconName::FileLock => "icons/file_icons/lock.svg",
240            IconName::FileRust => "icons/file_icons/rust.svg",
241            IconName::FileToml => "icons/file_icons/toml.svg",
242            IconName::FileTree => "icons/project.svg",
243            IconName::Filter => "icons/filter.svg",
244            IconName::Folder => "icons/file_icons/folder.svg",
245            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
246            IconName::FolderX => "icons/stop_sharing.svg",
247            IconName::Github => "icons/github.svg",
248            IconName::Hash => "icons/hash.svg",
249            IconName::Indicator => "icons/indicator.svg",
250            IconName::IndicatorX => "icons/indicator_x.svg",
251            IconName::InlayHint => "icons/inlay_hint.svg",
252            IconName::Link => "icons/link.svg",
253            IconName::MagicWand => "icons/magic_wand.svg",
254            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
255            IconName::MailOpen => "icons/mail_open.svg",
256            IconName::Maximize => "icons/maximize.svg",
257            IconName::Menu => "icons/menu.svg",
258            IconName::MessageBubbles => "icons/conversations.svg",
259            IconName::Mic => "icons/mic.svg",
260            IconName::MicMute => "icons/mic_mute.svg",
261            IconName::Minimize => "icons/minimize.svg",
262            IconName::Option => "icons/option.svg",
263            IconName::PageDown => "icons/page_down.svg",
264            IconName::PageUp => "icons/page_up.svg",
265            IconName::Person => "icons/person.svg",
266            IconName::Pencil => "icons/pencil.svg",
267            IconName::Play => "icons/play.svg",
268            IconName::Plus => "icons/plus.svg",
269            IconName::Public => "icons/public.svg",
270            IconName::Quote => "icons/quote.svg",
271            IconName::Regex => "icons/regex.svg",
272            IconName::Replace => "icons/replace.svg",
273            IconName::ReplaceAll => "icons/replace_all.svg",
274            IconName::ReplaceNext => "icons/replace_next.svg",
275            IconName::Return => "icons/return.svg",
276            IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
277            IconName::Settings => "icons/file_icons/settings.svg",
278            IconName::Sliders => "icons/sliders.svg",
279            IconName::Screen => "icons/desktop.svg",
280            IconName::SelectAll => "icons/select_all.svg",
281            IconName::Server => "icons/server.svg",
282            IconName::Shift => "icons/shift.svg",
283            IconName::Snip => "icons/snip.svg",
284            IconName::Space => "icons/space.svg",
285            IconName::Split => "icons/split.svg",
286            IconName::Spinner => "icons/spinner.svg",
287            IconName::Supermaven => "icons/supermaven.svg",
288            IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
289            IconName::SupermavenError => "icons/supermaven_error.svg",
290            IconName::SupermavenInit => "icons/supermaven_init.svg",
291            IconName::Strikethrough => "icons/strikethrough.svg",
292            IconName::Tab => "icons/tab.svg",
293            IconName::Terminal => "icons/terminal.svg",
294            IconName::Trash => "icons/trash.svg",
295            IconName::Update => "icons/update.svg",
296            IconName::WholeWord => "icons/word_search.svg",
297            IconName::XCircle => "icons/error.svg",
298            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
299            IconName::ZedAssistant => "icons/zed_assistant.svg",
300            IconName::PullRequest => "icons/pull_request.svg",
301            IconName::HistoryRerun => "icons/history_rerun.svg",
302        }
303    }
304}
305
306#[derive(IntoElement)]
307pub struct Icon {
308    path: SharedString,
309    color: Color,
310    size: Rems,
311    transformation: Transformation,
312}
313
314impl Icon {
315    pub fn new(icon: IconName) -> Self {
316        Self {
317            path: icon.path().into(),
318            color: Color::default(),
319            size: IconSize::default().rems(),
320            transformation: Transformation::default(),
321        }
322    }
323
324    pub fn from_path(path: impl Into<SharedString>) -> Self {
325        Self {
326            path: path.into(),
327            color: Color::default(),
328            size: IconSize::default().rems(),
329            transformation: Transformation::default(),
330        }
331    }
332
333    pub fn color(mut self, color: Color) -> Self {
334        self.color = color;
335        self
336    }
337
338    pub fn size(mut self, size: IconSize) -> Self {
339        self.size = size.rems();
340        self
341    }
342
343    /// Sets a custom size for the icon, in [`Rems`].
344    ///
345    /// Not to be exposed outside of the `ui` crate.
346    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
347        self.size = size;
348        self
349    }
350
351    pub fn font_size(self, font_size: AbsoluteLength) -> Self {
352        let rems = match font_size {
353            AbsoluteLength::Pixels(pixels) => rems_from_px(pixels.into()),
354            AbsoluteLength::Rems(rems) => rems,
355        };
356        self.custom_size(rems)
357    }
358
359    pub fn transform(mut self, transformation: Transformation) -> Self {
360        self.transformation = transformation;
361        self
362    }
363}
364
365impl RenderOnce for Icon {
366    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
367        svg()
368            .with_transformation(self.transformation)
369            .size(self.size)
370            .flex_none()
371            .path(self.path)
372            .text_color(self.color.color(cx))
373    }
374}
375
376#[derive(IntoElement)]
377pub struct DecoratedIcon {
378    icon: Icon,
379    decoration: IconDecoration,
380    decoration_color: Color,
381    parent_background: Option<Hsla>,
382}
383
384impl DecoratedIcon {
385    pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
386        Self {
387            icon,
388            decoration,
389            decoration_color: Color::Default,
390            parent_background: None,
391        }
392    }
393
394    pub fn decoration_color(mut self, color: Color) -> Self {
395        self.decoration_color = color;
396        self
397    }
398
399    pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
400        self.parent_background = background;
401        self
402    }
403}
404
405impl RenderOnce for DecoratedIcon {
406    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
407        let background = self
408            .parent_background
409            .unwrap_or(cx.theme().colors().background);
410
411        let size = self.icon.size;
412
413        let decoration_icon = match self.decoration {
414            IconDecoration::Strikethrough => IconName::Strikethrough,
415            IconDecoration::IndicatorDot => IconName::Indicator,
416            IconDecoration::X => IconName::IndicatorX,
417        };
418
419        let decoration_svg = |icon: IconName| {
420            svg()
421                .absolute()
422                .top_0()
423                .left_0()
424                .path(icon.path())
425                .size(size)
426                .flex_none()
427                .text_color(self.decoration_color.color(cx))
428        };
429
430        let decoration_knockout = |icon: IconName| {
431            svg()
432                .absolute()
433                .top(-rems_from_px(2.))
434                .left(-rems_from_px(3.))
435                .path(icon.path())
436                .size(size + rems_from_px(2.))
437                .flex_none()
438                .text_color(background)
439        };
440
441        div()
442            .relative()
443            .size(self.icon.size)
444            .child(self.icon)
445            .child(decoration_knockout(decoration_icon))
446            .child(decoration_svg(decoration_icon))
447    }
448}
449
450#[derive(IntoElement)]
451pub struct IconWithIndicator {
452    icon: Icon,
453    indicator: Option<Indicator>,
454    indicator_border_color: Option<Hsla>,
455}
456
457impl IconWithIndicator {
458    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
459        Self {
460            icon,
461            indicator,
462            indicator_border_color: None,
463        }
464    }
465
466    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
467        self.indicator = indicator;
468        self
469    }
470
471    pub fn indicator_color(mut self, color: Color) -> Self {
472        if let Some(indicator) = self.indicator.as_mut() {
473            indicator.color = color;
474        }
475        self
476    }
477
478    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
479        self.indicator_border_color = color;
480        self
481    }
482}
483
484impl RenderOnce for IconWithIndicator {
485    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
486        let indicator_border_color = self
487            .indicator_border_color
488            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
489
490        div()
491            .relative()
492            .child(self.icon)
493            .when_some(self.indicator, |this, indicator| {
494                this.child(
495                    div()
496                        .absolute()
497                        .w_2()
498                        .h_2()
499                        .border_1()
500                        .border_color(indicator_border_color)
501                        .rounded_full()
502                        .bottom_neg_0p5()
503                        .right_neg_1()
504                        .child(indicator),
505                )
506            })
507    }
508}