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