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