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