icon.rs

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