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