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