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