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