icon.rs

  1#![allow(missing_docs)]
  2
  3mod decorated_icon;
  4mod icon_decoration;
  5
  6pub use decorated_icon::*;
  7use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
  8pub use icon_decoration::*;
  9use serde::{Deserialize, Serialize};
 10use strum::{EnumIter, EnumString, IntoStaticStr};
 11use ui_macros::DerivePathStr;
 12
 13use crate::{
 14    prelude::*,
 15    traits::component_preview::{ComponentExample, ComponentPreview},
 16    Indicator,
 17};
 18
 19#[derive(IntoElement)]
 20pub enum AnyIcon {
 21    Icon(Icon),
 22    AnimatedIcon(AnimationElement<Icon>),
 23}
 24
 25impl AnyIcon {
 26    /// Returns a new [`AnyIcon`] after applying the given mapping function
 27    /// to the contained [`Icon`].
 28    pub fn map(self, f: impl FnOnce(Icon) -> Icon) -> Self {
 29        match self {
 30            Self::Icon(icon) => Self::Icon(f(icon)),
 31            Self::AnimatedIcon(animated_icon) => Self::AnimatedIcon(animated_icon.map_element(f)),
 32        }
 33    }
 34}
 35
 36impl From<Icon> for AnyIcon {
 37    fn from(value: Icon) -> Self {
 38        Self::Icon(value)
 39    }
 40}
 41
 42impl From<AnimationElement<Icon>> for AnyIcon {
 43    fn from(value: AnimationElement<Icon>) -> Self {
 44        Self::AnimatedIcon(value)
 45    }
 46}
 47
 48impl RenderOnce for AnyIcon {
 49    fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
 50        match self {
 51            Self::Icon(icon) => icon.into_any_element(),
 52            Self::AnimatedIcon(animated_icon) => animated_icon.into_any_element(),
 53        }
 54    }
 55}
 56
 57#[derive(Default, PartialEq, Copy, Clone)]
 58pub enum IconSize {
 59    /// 10px
 60    Indicator,
 61    /// 12px
 62    XSmall,
 63    /// 14px
 64    Small,
 65    #[default]
 66    /// 16px
 67    Medium,
 68}
 69
 70impl IconSize {
 71    pub fn rems(self) -> Rems {
 72        match self {
 73            IconSize::Indicator => rems_from_px(10.),
 74            IconSize::XSmall => rems_from_px(12.),
 75            IconSize::Small => rems_from_px(14.),
 76            IconSize::Medium => rems_from_px(16.),
 77        }
 78    }
 79
 80    /// Returns the individual components of the square that contains this [`IconSize`].
 81    ///
 82    /// The returned tuple contains:
 83    ///   1. The length of one side of the square
 84    ///   2. The padding of one side of the square
 85    pub fn square_components(&self, cx: &mut WindowContext) -> (Pixels, Pixels) {
 86        let icon_size = self.rems() * cx.rem_size();
 87        let padding = match self {
 88            IconSize::Indicator => DynamicSpacing::Base00.px(cx),
 89            IconSize::XSmall => DynamicSpacing::Base02.px(cx),
 90            IconSize::Small => DynamicSpacing::Base02.px(cx),
 91            IconSize::Medium => DynamicSpacing::Base02.px(cx),
 92        };
 93
 94        (icon_size, padding)
 95    }
 96
 97    /// Returns the length of a side of the square that contains this [`IconSize`], with padding.
 98    pub fn square(&self, cx: &mut WindowContext) -> Pixels {
 99        let (icon_size, padding) = self.square_components(cx);
100
101        icon_size + padding * 2.
102    }
103}
104
105#[derive(
106    Debug,
107    PartialEq,
108    Eq,
109    Copy,
110    Clone,
111    EnumIter,
112    EnumString,
113    IntoStaticStr,
114    Serialize,
115    Deserialize,
116    DerivePathStr,
117)]
118#[strum(serialize_all = "snake_case")]
119#[path_str(prefix = "icons", suffix = ".svg")]
120pub enum IconName {
121    Ai,
122    AiAnthropic,
123    AiAnthropicHosted,
124    AiGoogle,
125    AiLmStudio,
126    AiOllama,
127    AiOpenAi,
128    AiZed,
129    ArrowCircle,
130    ArrowDown,
131    ArrowDownFromLine,
132    ArrowLeft,
133    ArrowRight,
134    ArrowUp,
135    ArrowUpFromLine,
136    ArrowUpRight,
137    AtSign,
138    AudioOff,
139    AudioOn,
140    Backspace,
141    Bell,
142    BellDot,
143    BellOff,
144    BellRing,
145    Blocks,
146    Bolt,
147    Book,
148    BookCopy,
149    BookPlus,
150    CaseSensitive,
151    Check,
152    ChevronDown,
153    /// This chevron indicates a popover menu.
154    ChevronDownSmall,
155    ChevronLeft,
156    ChevronRight,
157    ChevronUp,
158    ChevronUpDown,
159    Close,
160    Code,
161    Command,
162    Context,
163    Control,
164    Copilot,
165    CopilotDisabled,
166    CopilotError,
167    CopilotInit,
168    Copy,
169    CountdownTimer,
170    CursorIBeam,
171    Dash,
172    DatabaseZap,
173    Delete,
174    Diff,
175    Disconnected,
176    Download,
177    Ellipsis,
178    EllipsisVertical,
179    Envelope,
180    Eraser,
181    Escape,
182    ExpandVertical,
183    Exit,
184    ExternalLink,
185    Eye,
186    File,
187    FileCode,
188    FileDoc,
189    FileDiff,
190    FileGeneric,
191    FileGit,
192    FileLock,
193    FileRust,
194    FileSearch,
195    FileText,
196    FileToml,
197    FileTree,
198    Filter,
199    Folder,
200    FolderOpen,
201    FolderX,
202    Font,
203    FontSize,
204    FontWeight,
205    GenericClose,
206    GenericMaximize,
207    GenericMinimize,
208    GenericRestore,
209    Github,
210    Globe,
211    GitBranch,
212    Hash,
213    HistoryRerun,
214    Indicator,
215    IndicatorX,
216    Info,
217    InlayHint,
218    Keyboard,
219    Library,
220    LineHeight,
221    Link,
222    ListTree,
223    ListX,
224    MagnifyingGlass,
225    MailOpen,
226    Maximize,
227    Menu,
228    MessageBubbles,
229    MessageCircle,
230    Mic,
231    MicMute,
232    Microscope,
233    Minimize,
234    Option,
235    PageDown,
236    PageUp,
237    PanelLeft,
238    PanelRight,
239    Pencil,
240    Person,
241    PersonCircle,
242    PhoneIncoming,
243    Pin,
244    Play,
245    Plus,
246    PocketKnife,
247    Public,
248    PullRequest,
249    Quote,
250    RefreshTitle,
251    Regex,
252    ReplNeutral,
253    Replace,
254    ReplaceAll,
255    ReplaceNext,
256    ReplyArrowRight,
257    Rerun,
258    Return,
259    Reveal,
260    RotateCcw,
261    RotateCw,
262    Route,
263    Save,
264    Screen,
265    SearchCode,
266    SearchSelection,
267    SelectAll,
268    Server,
269    Settings,
270    SettingsAlt,
271    Shift,
272    Slash,
273    SlashSquare,
274    Sliders,
275    SlidersVertical,
276    Snip,
277    Space,
278    Sparkle,
279    SparkleAlt,
280    SparkleFilled,
281    Spinner,
282    Split,
283    SquareDot,
284    SquareMinus,
285    SquarePlus,
286    Star,
287    StarFilled,
288    Stop,
289    Strikethrough,
290    Supermaven,
291    SupermavenDisabled,
292    SupermavenError,
293    SupermavenInit,
294    SwatchBook,
295    Tab,
296    Terminal,
297    TextSnippet,
298    ThumbsUp,
299    ThumbsDown,
300    Trash,
301    TrashAlt,
302    Triangle,
303    TriangleRight,
304    Undo,
305    Unpin,
306    Update,
307    UserGroup,
308    Visible,
309    Wand,
310    Warning,
311    WholeWord,
312    X,
313    XCircle,
314    ZedAssistant,
315    ZedAssistant2,
316    ZedAssistantFilled,
317    ZedPredict,
318    ZedXCopilot,
319}
320
321impl From<IconName> for Icon {
322    fn from(icon: IconName) -> Self {
323        Icon::new(icon)
324    }
325}
326
327#[derive(IntoElement)]
328pub struct Icon {
329    path: SharedString,
330    color: Color,
331    size: Rems,
332    transformation: Transformation,
333}
334
335impl Icon {
336    pub fn new(icon: IconName) -> Self {
337        Self {
338            path: icon.path().into(),
339            color: Color::default(),
340            size: IconSize::default().rems(),
341            transformation: Transformation::default(),
342        }
343    }
344
345    pub fn from_path(path: impl Into<SharedString>) -> Self {
346        Self {
347            path: path.into(),
348            color: Color::default(),
349            size: IconSize::default().rems(),
350            transformation: Transformation::default(),
351        }
352    }
353
354    pub fn color(mut self, color: Color) -> Self {
355        self.color = color;
356        self
357    }
358
359    pub fn size(mut self, size: IconSize) -> Self {
360        self.size = size.rems();
361        self
362    }
363
364    /// Sets a custom size for the icon, in [`Rems`].
365    ///
366    /// Not to be exposed outside of the `ui` crate.
367    pub(crate) fn custom_size(mut self, size: Rems) -> Self {
368        self.size = size;
369        self
370    }
371
372    pub fn transform(mut self, transformation: Transformation) -> Self {
373        self.transformation = transformation;
374        self
375    }
376}
377
378impl RenderOnce for Icon {
379    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
380        svg()
381            .with_transformation(self.transformation)
382            .size(self.size)
383            .flex_none()
384            .path(self.path)
385            .text_color(self.color.color(cx))
386    }
387}
388
389#[derive(IntoElement)]
390pub struct IconWithIndicator {
391    icon: Icon,
392    indicator: Option<Indicator>,
393    indicator_border_color: Option<Hsla>,
394}
395
396impl IconWithIndicator {
397    pub fn new(icon: Icon, indicator: Option<Indicator>) -> Self {
398        Self {
399            icon,
400            indicator,
401            indicator_border_color: None,
402        }
403    }
404
405    pub fn indicator(mut self, indicator: Option<Indicator>) -> Self {
406        self.indicator = indicator;
407        self
408    }
409
410    pub fn indicator_color(mut self, color: Color) -> Self {
411        if let Some(indicator) = self.indicator.as_mut() {
412            indicator.color = color;
413        }
414        self
415    }
416
417    pub fn indicator_border_color(mut self, color: Option<Hsla>) -> Self {
418        self.indicator_border_color = color;
419        self
420    }
421}
422
423impl RenderOnce for IconWithIndicator {
424    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
425        let indicator_border_color = self
426            .indicator_border_color
427            .unwrap_or_else(|| cx.theme().colors().elevated_surface_background);
428
429        div()
430            .relative()
431            .child(self.icon)
432            .when_some(self.indicator, |this, indicator| {
433                this.child(
434                    div()
435                        .absolute()
436                        .size_2p5()
437                        .border_2()
438                        .border_color(indicator_border_color)
439                        .rounded_full()
440                        .bottom_neg_0p5()
441                        .right_neg_0p5()
442                        .child(indicator),
443                )
444            })
445    }
446}
447
448impl ComponentPreview for Icon {
449    fn examples(_cx: &mut WindowContext) -> Vec<ComponentExampleGroup<Icon>> {
450        let arrow_icons = vec![
451            IconName::ArrowDown,
452            IconName::ArrowLeft,
453            IconName::ArrowRight,
454            IconName::ArrowUp,
455            IconName::ArrowCircle,
456        ];
457
458        vec![example_group_with_title(
459            "Arrow Icons",
460            arrow_icons
461                .into_iter()
462                .map(|icon| {
463                    let name = format!("{:?}", icon).to_string();
464                    ComponentExample::new(name, Icon::new(icon))
465                })
466                .collect(),
467        )]
468    }
469}