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