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