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#[derive(Default, PartialEq, Copy, Clone)]
 45pub enum IconSize {
 46    Indicator,
 47    XSmall,
 48    Small,
 49    #[default]
 50    Medium,
 51}
 52
 53impl IconSize {
 54    pub fn rems(self) -> Rems {
 55        match self {
 56            IconSize::Indicator => rems_from_px(10.),
 57            IconSize::XSmall => rems_from_px(12.),
 58            IconSize::Small => rems_from_px(14.),
 59            IconSize::Medium => rems_from_px(16.),
 60        }
 61    }
 62}
 63
 64#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
 65pub enum IconName {
 66    Ai,
 67    ArrowDown,
 68    ArrowLeft,
 69    ArrowRight,
 70    ArrowUp,
 71    ArrowUpRight,
 72    ArrowCircle,
 73    AtSign,
 74    AudioOff,
 75    AudioOn,
 76    Backspace,
 77    Bell,
 78    BellOff,
 79    BellRing,
 80    BellDot,
 81    Bolt,
 82    CaseSensitive,
 83    Check,
 84    ChevronDown,
 85    ChevronLeft,
 86    ChevronRight,
 87    ChevronUp,
 88    ExpandVertical,
 89    Close,
 90    Collab,
 91    Command,
 92    Control,
 93    Copilot,
 94    CopilotDisabled,
 95    CopilotError,
 96    CopilotInit,
 97    Copy,
 98    Dash,
 99    Delete,
100    Disconnected,
101    Ellipsis,
102    Envelope,
103    Escape,
104    ExclamationTriangle,
105    Exit,
106    ExternalLink,
107    File,
108    FileDoc,
109    FileGeneric,
110    FileGit,
111    FileLock,
112    FileRust,
113    FileToml,
114    FileTree,
115    Filter,
116    Folder,
117    FolderOpen,
118    FolderX,
119    Github,
120    Hash,
121    InlayHint,
122    Link,
123    MagicWand,
124    MagnifyingGlass,
125    MailOpen,
126    Maximize,
127    Menu,
128    MessageBubbles,
129    Mic,
130    MicMute,
131    Minimize,
132    Option,
133    PageDown,
134    PageUp,
135    Pencil,
136    Person,
137    Play,
138    Plus,
139    Public,
140    Quote,
141    Regex,
142    Replace,
143    ReplaceAll,
144    ReplaceNext,
145    Return,
146    ReplyArrowRight,
147    Settings,
148    Sliders,
149    Screen,
150    SelectAll,
151    Server,
152    Shift,
153    Snip,
154    Space,
155    Split,
156    Tab,
157    Terminal,
158    Trash,
159    Update,
160    WholeWord,
161    XCircle,
162    ZedXCopilot,
163    ZedAssistant,
164    PullRequest,
165}
166
167impl IconName {
168    pub fn path(self) -> &'static str {
169        match self {
170            IconName::Ai => "icons/ai.svg",
171            IconName::ArrowDown => "icons/arrow_down.svg",
172            IconName::ArrowLeft => "icons/arrow_left.svg",
173            IconName::ArrowRight => "icons/arrow_right.svg",
174            IconName::ArrowUp => "icons/arrow_up.svg",
175            IconName::ArrowUpRight => "icons/arrow_up_right.svg",
176            IconName::ArrowCircle => "icons/arrow_circle.svg",
177            IconName::AtSign => "icons/at_sign.svg",
178            IconName::AudioOff => "icons/speaker_off.svg",
179            IconName::AudioOn => "icons/speaker_loud.svg",
180            IconName::Backspace => "icons/backspace.svg",
181            IconName::Bell => "icons/bell.svg",
182            IconName::BellOff => "icons/bell_off.svg",
183            IconName::BellRing => "icons/bell_ring.svg",
184            IconName::BellDot => "icons/bell_dot.svg",
185            IconName::Bolt => "icons/bolt.svg",
186            IconName::CaseSensitive => "icons/case_insensitive.svg",
187            IconName::Check => "icons/check.svg",
188            IconName::ChevronDown => "icons/chevron_down.svg",
189            IconName::ChevronLeft => "icons/chevron_left.svg",
190            IconName::ChevronRight => "icons/chevron_right.svg",
191            IconName::ChevronUp => "icons/chevron_up.svg",
192            IconName::ExpandVertical => "icons/expand_vertical.svg",
193            IconName::Close => "icons/x.svg",
194            IconName::Collab => "icons/user_group_16.svg",
195            IconName::Command => "icons/command.svg",
196            IconName::Control => "icons/control.svg",
197            IconName::Copilot => "icons/copilot.svg",
198            IconName::CopilotDisabled => "icons/copilot_disabled.svg",
199            IconName::CopilotError => "icons/copilot_error.svg",
200            IconName::CopilotInit => "icons/copilot_init.svg",
201            IconName::Copy => "icons/copy.svg",
202            IconName::Dash => "icons/dash.svg",
203            IconName::Delete => "icons/delete.svg",
204            IconName::Disconnected => "icons/disconnected.svg",
205            IconName::Ellipsis => "icons/ellipsis.svg",
206            IconName::Envelope => "icons/feedback.svg",
207            IconName::Escape => "icons/escape.svg",
208            IconName::ExclamationTriangle => "icons/warning.svg",
209            IconName::Exit => "icons/exit.svg",
210            IconName::ExternalLink => "icons/external_link.svg",
211            IconName::File => "icons/file.svg",
212            IconName::FileDoc => "icons/file_icons/book.svg",
213            IconName::FileGeneric => "icons/file_icons/file.svg",
214            IconName::FileGit => "icons/file_icons/git.svg",
215            IconName::FileLock => "icons/file_icons/lock.svg",
216            IconName::FileRust => "icons/file_icons/rust.svg",
217            IconName::FileToml => "icons/file_icons/toml.svg",
218            IconName::FileTree => "icons/project.svg",
219            IconName::Filter => "icons/filter.svg",
220            IconName::Folder => "icons/file_icons/folder.svg",
221            IconName::FolderOpen => "icons/file_icons/folder_open.svg",
222            IconName::FolderX => "icons/stop_sharing.svg",
223            IconName::Github => "icons/github.svg",
224            IconName::Hash => "icons/hash.svg",
225            IconName::InlayHint => "icons/inlay_hint.svg",
226            IconName::Link => "icons/link.svg",
227            IconName::MagicWand => "icons/magic_wand.svg",
228            IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
229            IconName::MailOpen => "icons/mail_open.svg",
230            IconName::Maximize => "icons/maximize.svg",
231            IconName::Menu => "icons/menu.svg",
232            IconName::MessageBubbles => "icons/conversations.svg",
233            IconName::Mic => "icons/mic.svg",
234            IconName::MicMute => "icons/mic_mute.svg",
235            IconName::Minimize => "icons/minimize.svg",
236            IconName::Option => "icons/option.svg",
237            IconName::PageDown => "icons/page_down.svg",
238            IconName::PageUp => "icons/page_up.svg",
239            IconName::Person => "icons/person.svg",
240            IconName::Pencil => "icons/pencil.svg",
241            IconName::Play => "icons/play.svg",
242            IconName::Plus => "icons/plus.svg",
243            IconName::Public => "icons/public.svg",
244            IconName::Quote => "icons/quote.svg",
245            IconName::Regex => "icons/regex.svg",
246            IconName::Replace => "icons/replace.svg",
247            IconName::ReplaceAll => "icons/replace_all.svg",
248            IconName::ReplaceNext => "icons/replace_next.svg",
249            IconName::Return => "icons/return.svg",
250            IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
251            IconName::Settings => "icons/file_icons/settings.svg",
252            IconName::Sliders => "icons/sliders.svg",
253            IconName::Screen => "icons/desktop.svg",
254            IconName::SelectAll => "icons/select_all.svg",
255            IconName::Server => "icons/server.svg",
256            IconName::Shift => "icons/shift.svg",
257            IconName::Snip => "icons/snip.svg",
258            IconName::Space => "icons/space.svg",
259            IconName::Split => "icons/split.svg",
260            IconName::Tab => "icons/tab.svg",
261            IconName::Terminal => "icons/terminal.svg",
262            IconName::Trash => "icons/trash.svg",
263            IconName::Update => "icons/update.svg",
264            IconName::WholeWord => "icons/word_search.svg",
265            IconName::XCircle => "icons/error.svg",
266            IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
267            IconName::ZedAssistant => "icons/zed_assistant.svg",
268            IconName::PullRequest => "icons/pull_request.svg",
269        }
270    }
271}
272
273#[derive(IntoElement)]
274pub struct Icon {
275    path: SharedString,
276    color: Color,
277    size: Rems,
278    transformation: Transformation,
279}
280
281impl Icon {
282    pub fn new(icon: IconName) -> Self {
283        Self {
284            path: icon.path().into(),
285            color: Color::default(),
286            size: IconSize::default().rems(),
287            transformation: Transformation::default(),
288        }
289    }
290
291    pub fn from_path(path: impl Into<SharedString>) -> Self {
292        Self {
293            path: path.into(),
294            color: Color::default(),
295            size: IconSize::default().rems(),
296            transformation: Transformation::default(),
297        }
298    }
299
300    pub fn color(mut self, color: Color) -> Self {
301        self.color = color;
302        self
303    }
304
305    pub fn size(mut self, size: IconSize) -> Self {
306        self.size = size.rems();
307        self
308    }
309
310    /// Sets a custom size for the icon, in [`Rems`].
311    ///
312    /// Not to be exposed outside of the `ui` crate.
313    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
314        self.size = size;
315        self
316    }
317
318    pub fn transform(mut self, transformation: Transformation) -> Self {
319        self.transformation = transformation;
320        self
321    }
322}
323
324impl RenderOnce for Icon {
325    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
326        svg()
327            .with_transformation(self.transformation)
328            .size(self.size)
329            .flex_none()
330            .path(self.path)
331            .text_color(self.color.color(cx))
332    }
333}
334
335#[derive(IntoElement)]
336pub struct IconWithIndicator {
337    icon: Icon,
338    indicator: Option<Indicator>,
339    indicator_border_color: Option<Hsla>,
340}
341
342impl IconWithIndicator {
343    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
344        Self {
345            icon,
346            indicator,
347            indicator_border_color: None,
348        }
349    }
350
351    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
352        self.indicator = indicator;
353        self
354    }
355
356    pub fn indicator_color(mut self, color: Color) -> Self {
357        if let Some(indicator) = self.indicator.as_mut() {
358            indicator.color = color;
359        }
360        self
361    }
362
363    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
364        self.indicator_border_color = color;
365        self
366    }
367}
368
369impl RenderOnce for IconWithIndicator {
370    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
371        let indicator_border_color = self
372            .indicator_border_color
373            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
374
375        div()
376            .relative()
377            .child(self.icon)
378            .when_some(self.indicator, |this, indicator| {
379                this.child(
380                    div()
381                        .absolute()
382                        .w_2()
383                        .h_2()
384                        .border()
385                        .border_color(indicator_border_color)
386                        .rounded_full()
387                        .neg_bottom_0p5()
388                        .neg_right_1()
389                        .child(indicator),
390                )
391            })
392    }
393}