icon.rs

  1use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  2use serde::{Deserialize, Serialize};
  3use strum::EnumIter;
  4
  5use crate::{prelude::*, Indicator};
  6
  7#[derive(IntoElement)]
  8pub enum AnyIcon {
  9    Icon(Icon),
 10    AnimatedIcon(AnimationElement<Icon>),
 11}
 12
 13impl AnyIcon {
 14    /// Returns a new [`AnyIcon`] after applying the given mapping function
 15    /// to the contained [`Icon`].
 16    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
 17        match self {
 18            Self::Icon(icon) => Self::Icon(f(icon)),
 19            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
 20        }
 21    }
 22}
 23
 24impl From<Icon> for AnyIcon {
 25    fn from(value: Icon) -> Self {
 26        Self::Icon(value)
 27    }
 28}
 29
 30impl From<AnimationElement<Icon>> for AnyIcon {
 31    fn from(value: AnimationElement<Icon>) -> Self {
 32        Self::AnimatedIcon(value)
 33    }
 34}
 35
 36impl RenderOnce for AnyIcon {
 37    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 38        match self {
 39            Self::Icon(icon) => icon.into_any_element(),
 40            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
 41        }
 42    }
 43}
 44
 45/// The decoration for an icon.
 46///
 47/// For example, this can show an indicator, an "x",
 48/// or a diagonal strkethrough to indicate something is disabled.
 49#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 50pub enum IconDecoration {
 51    Strikethrough,
 52    IndicatorDot,
 53    X,
 54}
 55
 56#[derive(Default, PartialEq, Copy, Clone)]
 57pub enum IconSize {
 58    /// 10px
 59    Indicator,
 60    /// 12px
 61    XSmall,
 62    /// 14px
 63    Small,
 64    #[default]
 65    /// 16px
 66    Medium,
 67}
 68
 69impl IconSize {
 70    pub fn rems(self) -> Rems {
 71        match self {
 72            IconSize::Indicator => rems_from_px(10.),
 73            IconSize::XSmall => rems_from_px(12.),
 74            IconSize::Small => rems_from_px(14.),
 75            IconSize::Medium => rems_from_px(16.),
 76        }
 77    }
 78}
 79
 80#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
 81pub enum IconName {
 82    Ai,
 83    ArrowCircle,
 84    ArrowDown,
 85    ArrowDownFromLine,
 86    ArrowLeft,
 87    ArrowRight,
 88    ArrowUp,
 89    ArrowUpFromLine,
 90    ArrowUpRight,
 91    AtSign,
 92    AudioOff,
 93    AudioOn,
 94    Backspace,
 95    Bell,
 96    BellDot,
 97    BellOff,
 98    BellRing,
 99    Bolt,
100    CaseSensitive,
101    Check,
102    ChevronDown,
103    ChevronLeft,
104    ChevronRight,
105    ChevronUp,
106    Close,
107    Code,
108    Collab,
109    Command,
110    Context,
111    Control,
112    Copilot,
113    CopilotDisabled,
114    CopilotError,
115    CopilotInit,
116    Copy,
117    CountdownTimer,
118    Dash,
119    Delete,
120    Disconnected,
121    Download,
122    Ellipsis,
123    Envelope,
124    Escape,
125    ExclamationTriangle,
126    Exit,
127    ExpandVertical,
128    ExternalLink,
129    File,
130    FileDoc,
131    FileGeneric,
132    FileGit,
133    FileLock,
134    FileRust,
135    FileToml,
136    FileTree,
137    Filter,
138    Folder,
139    FolderOpen,
140    FolderX,
141    Github,
142    Hash,
143    HistoryRerun,
144    Indicator,
145    IndicatorX,
146    InlayHint,
147    Library,
148    Link,
149    ListTree,
150    MagicWand,
151    MagnifyingGlass,
152    MailOpen,
153    Maximize,
154    Menu,
155    MessageBubbles,
156    Mic,
157    MicMute,
158    Minimize,
159    Option,
160    PageDown,
161    PageUp,
162    Pencil,
163    Person,
164    Play,
165    Plus,
166    Public,
167    PullRequest,
168    Quote,
169    Regex,
170    Replace,
171    ReplaceAll,
172    ReplaceNext,
173    ReplyArrowRight,
174    Rerun,
175    Return,
176    Reveal,
177    RotateCw,
178    Save,
179    Screen,
180    SelectAll,
181    SearchSelection,
182    Server,
183    Settings,
184    Shift,
185    Sliders,
186    Snip,
187    Space,
188    Sparkle,
189    SparkleFilled,
190    Spinner,
191    Split,
192    Star,
193    StarFilled,
194    Stop,
195    Strikethrough,
196    Supermaven,
197    SupermavenDisabled,
198    SupermavenError,
199    SupermavenInit,
200    Tab,
201    Terminal,
202    Trash,
203    TriangleRight,
204    Update,
205    WholeWord,
206    XCircle,
207    ZedAssistant,
208    ZedAssistantFilled,
209    ZedXCopilot,
210}
211
212impl IconName {
213    pub fn path(self) -> &'static str {
214        match self {
215            IconName::Ai => "icons/ai.svg",
216            IconName::ArrowCircle => "icons/arrow_circle.svg",
217            IconName::ArrowDown => "icons/arrow_down.svg",
218            IconName::ArrowDownFromLine => "icons/arrow_down_from_line.svg",
219            IconName::ArrowLeft => "icons/arrow_left.svg",
220            IconName::ArrowRight => "icons/arrow_right.svg",
221            IconName::ArrowUp => "icons/arrow_up.svg",
222            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
223            IconName::AtSign => "icons/at_sign.svg",
224            IconName::AudioOff => "icons/speaker_off.svg",
225            IconName::AudioOn => "icons/speaker_loud.svg",
226            IconName::Backspace => "icons/backspace.svg",
227            IconName::Bell => "icons/bell.svg",
228            IconName::BellDot => "icons/bell_dot.svg",
229            IconName::BellOff => "icons/bell_off.svg",
230            IconName::BellRing => "icons/bell_ring.svg",
231            IconName::Bolt => "icons/bolt.svg",
232            IconName::CaseSensitive => "icons/case_insensitive.svg",
233            IconName::Check => "icons/check.svg",
234            IconName::ChevronDown => "icons/chevron_down.svg",
235            IconName::ChevronLeft => "icons/chevron_left.svg",
236            IconName::ChevronRight => "icons/chevron_right.svg",
237            IconName::ChevronUp => "icons/chevron_up.svg",
238            IconName::Close => "icons/x.svg",
239            IconName::Code => "icons/code.svg",
240            IconName::Collab => "icons/user_group_16.svg",
241            IconName::Command => "icons/command.svg",
242            IconName::Context => "icons/context.svg",
243            IconName::Control => "icons/control.svg",
244            IconName::Copilot => "icons/copilot.svg",
245            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
246            IconName::CopilotError => "icons/copilot_error.svg",
247            IconName::CopilotInit => "icons/copilot_init.svg",
248            IconName::Copy => "icons/copy.svg",
249            IconName::CountdownTimer => "icons/countdown_timer.svg",
250            IconName::Dash => "icons/dash.svg",
251            IconName::Delete => "icons/delete.svg",
252            IconName::Disconnected => "icons/disconnected.svg",
253            IconName::Download => "icons/download.svg",
254            IconName::Ellipsis => "icons/ellipsis.svg",
255            IconName::Envelope => "icons/feedback.svg",
256            IconName::Escape => "icons/escape.svg",
257            IconName::ExclamationTriangle => "icons/warning.svg",
258            IconName::Exit => "icons/exit.svg",
259            IconName::ExpandVertical => "icons/expand_vertical.svg",
260            IconName::ExternalLink => "icons/external_link.svg",
261            IconName::File => "icons/file.svg",
262            IconName::FileDoc => "icons/file_icons/book.svg",
263            IconName::FileGeneric => "icons/file_icons/file.svg",
264            IconName::FileGit => "icons/file_icons/git.svg",
265            IconName::FileLock => "icons/file_icons/lock.svg",
266            IconName::FileRust => "icons/file_icons/rust.svg",
267            IconName::FileToml => "icons/file_icons/toml.svg",
268            IconName::FileTree => "icons/project.svg",
269            IconName::Filter => "icons/filter.svg",
270            IconName::Folder => "icons/file_icons/folder.svg",
271            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
272            IconName::FolderX => "icons/stop_sharing.svg",
273            IconName::Github => "icons/github.svg",
274            IconName::Hash => "icons/hash.svg",
275            IconName::HistoryRerun => "icons/history_rerun.svg",
276            IconName::Indicator => "icons/indicator.svg",
277            IconName::IndicatorX => "icons/indicator_x.svg",
278            IconName::InlayHint => "icons/inlay_hint.svg",
279            IconName::Library => "icons/library.svg",
280            IconName::Link => "icons/link.svg",
281            IconName::ListTree => "icons/list_tree.svg",
282            IconName::MagicWand => "icons/magic_wand.svg",
283            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
284            IconName::MailOpen => "icons/mail_open.svg",
285            IconName::Maximize => "icons/maximize.svg",
286            IconName::Menu => "icons/menu.svg",
287            IconName::MessageBubbles => "icons/conversations.svg",
288            IconName::Mic => "icons/mic.svg",
289            IconName::MicMute => "icons/mic_mute.svg",
290            IconName::Minimize => "icons/minimize.svg",
291            IconName::Option => "icons/option.svg",
292            IconName::PageDown => "icons/page_down.svg",
293            IconName::PageUp => "icons/page_up.svg",
294            IconName::Pencil => "icons/pencil.svg",
295            IconName::Person => "icons/person.svg",
296            IconName::Play => "icons/play.svg",
297            IconName::Plus => "icons/plus.svg",
298            IconName::Public => "icons/public.svg",
299            IconName::PullRequest => "icons/pull_request.svg",
300            IconName::Quote => "icons/quote.svg",
301            IconName::Regex => "icons/regex.svg",
302            IconName::Replace => "icons/replace.svg",
303            IconName::Reveal => "icons/reveal.svg",
304            IconName::ReplaceAll => "icons/replace_all.svg",
305            IconName::ReplaceNext => "icons/replace_next.svg",
306            IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
307            IconName::Rerun => "icons/rerun.svg",
308            IconName::Return => "icons/return.svg",
309            IconName::RotateCw => "icons/rotate_cw.svg",
310            IconName::Save => "icons/save.svg",
311            IconName::Screen => "icons/desktop.svg",
312            IconName::SelectAll => "icons/select_all.svg",
313            IconName::SearchSelection => "icons/search_selection.svg",
314            IconName::Server => "icons/server.svg",
315            IconName::Settings => "icons/file_icons/settings.svg",
316            IconName::Shift => "icons/shift.svg",
317            IconName::Sliders => "icons/sliders.svg",
318            IconName::Snip => "icons/snip.svg",
319            IconName::Space => "icons/space.svg",
320            IconName::Sparkle => "icons/sparkle.svg",
321            IconName::SparkleFilled => "icons/sparkle_filled.svg",
322            IconName::Spinner => "icons/spinner.svg",
323            IconName::Split => "icons/split.svg",
324            IconName::Star => "icons/star.svg",
325            IconName::StarFilled => "icons/star_filled.svg",
326            IconName::Stop => "icons/stop.svg",
327            IconName::Strikethrough => "icons/strikethrough.svg",
328            IconName::Supermaven => "icons/supermaven.svg",
329            IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",
330            IconName::SupermavenError => "icons/supermaven_error.svg",
331            IconName::SupermavenInit => "icons/supermaven_init.svg",
332            IconName::Tab => "icons/tab.svg",
333            IconName::Terminal => "icons/terminal.svg",
334            IconName::Trash => "icons/trash.svg",
335            IconName::TriangleRight => "icons/triangle_right.svg",
336            IconName::Update => "icons/update.svg",
337            IconName::WholeWord => "icons/word_search.svg",
338            IconName::XCircle => "icons/error.svg",
339            IconName::ZedAssistant => "icons/zed_assistant.svg",
340            IconName::ZedAssistantFilled => "icons/zed_assistant_filled.svg",
341            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
342            IconName::ArrowUpFromLine => "icons/arrow_up_from_line.svg",
343        }
344    }
345}
346
347#[derive(IntoElement)]
348pub struct Icon {
349    path: SharedString,
350    color: Color,
351    size: Rems,
352    transformation: Transformation,
353}
354
355impl Icon {
356    pub fn new(icon: IconName) -> Self {
357        Self {
358            path: icon.path().into(),
359            color: Color::default(),
360            size: IconSize::default().rems(),
361            transformation: Transformation::default(),
362        }
363    }
364
365    pub fn from_path(path: impl Into<SharedString>) -> Self {
366        Self {
367            path: path.into(),
368            color: Color::default(),
369            size: IconSize::default().rems(),
370            transformation: Transformation::default(),
371        }
372    }
373
374    pub fn color(mut self, color: Color) -> Self {
375        self.color = color;
376        self
377    }
378
379    pub fn size(mut self, size: IconSize) -> Self {
380        self.size = size.rems();
381        self
382    }
383
384    /// Sets a custom size for the icon, in [`Rems`].
385    ///
386    /// Not to be exposed outside of the `ui` crate.
387    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
388        self.size = size;
389        self
390    }
391
392    pub fn transform(mut self, transformation: Transformation) -> Self {
393        self.transformation = transformation;
394        self
395    }
396}
397
398impl RenderOnce for Icon {
399    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
400        svg()
401            .with_transformation(self.transformation)
402            .size(self.size)
403            .flex_none()
404            .path(self.path)
405            .text_color(self.color.color(cx))
406    }
407}
408
409#[derive(IntoElement)]
410pub struct DecoratedIcon {
411    icon: Icon,
412    decoration: IconDecoration,
413    decoration_color: Color,
414    parent_background: Option<Hsla>,
415}
416
417impl DecoratedIcon {
418    pub fn new(icon: Icon, decoration: IconDecoration) -> Self {
419        Self {
420            icon,
421            decoration,
422            decoration_color: Color::Default,
423            parent_background: None,
424        }
425    }
426
427    pub fn decoration_color(mut self, color: Color) -> Self {
428        self.decoration_color = color;
429        self
430    }
431
432    pub fn parent_background(mut self, background: Option<Hsla>) -> Self {
433        self.parent_background = background;
434        self
435    }
436}
437
438impl RenderOnce for DecoratedIcon {
439    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
440        let background = self
441            .parent_background
442            .unwrap_or(cx.theme().colors().background);
443
444        let size = self.icon.size;
445
446        let decoration_icon = match self.decoration {
447            IconDecoration::Strikethrough => IconName::Strikethrough,
448            IconDecoration::IndicatorDot => IconName::Indicator,
449            IconDecoration::X => IconName::IndicatorX,
450        };
451
452        let decoration_svg = |icon: IconName| {
453            svg()
454                .absolute()
455                .top_0()
456                .left_0()
457                .path(icon.path())
458                .size(size)
459                .flex_none()
460                .text_color(self.decoration_color.color(cx))
461        };
462
463        let decoration_knockout = |icon: IconName| {
464            svg()
465                .absolute()
466                .top(-rems_from_px(2.))
467                .left(-rems_from_px(3.))
468                .path(icon.path())
469                .size(size + rems_from_px(2.))
470                .flex_none()
471                .text_color(background)
472        };
473
474        div()
475            .relative()
476            .size(self.icon.size)
477            .child(self.icon)
478            .child(decoration_knockout(decoration_icon))
479            .child(decoration_svg(decoration_icon))
480    }
481}
482
483#[derive(IntoElement)]
484pub struct IconWithIndicator {
485    icon: Icon,
486    indicator: Option<Indicator>,
487    indicator_border_color: Option<Hsla>,
488}
489
490impl IconWithIndicator {
491    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
492        Self {
493            icon,
494            indicator,
495            indicator_border_color: None,
496        }
497    }
498
499    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
500        self.indicator = indicator;
501        self
502    }
503
504    pub fn indicator_color(mut self, color: Color) -> Self {
505        if let Some(indicator) = self.indicator.as_mut() {
506            indicator.color = color;
507        }
508        self
509    }
510
511    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
512        self.indicator_border_color = color;
513        self
514    }
515}
516
517impl RenderOnce for IconWithIndicator {
518    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
519        let indicator_border_color = self
520            .indicator_border_color
521            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
522
523        div()
524            .relative()
525            .child(self.icon)
526            .when_some(self.indicator, |this, indicator| {
527                this.child(
528                    div()
529                        .absolute()
530                        .w_2()
531                        .h_2()
532                        .border_1()
533                        .border_color(indicator_border_color)
534                        .rounded_full()
535                        .bottom_neg_0p5()
536                        .right_neg_1()
537                        .child(indicator),
538                )
539            })
540    }
541}