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