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