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