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