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