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