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