avatar.rs

  1use crate::prelude::*;
  2use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};
  3
  4/// The shape of an [`Avatar`].
  5#[derive(Debug, Default, PartialEq, Clone)]
  6pub enum AvatarShape {
  7    /// The avatar is shown in a circle.
  8    #[default]
  9    Circle,
 10    /// The avatar is shown in a rectangle with rounded corners.
 11    RoundedRectangle,
 12}
 13
 14/// An element that renders a user avatar with customizable appearance options.
 15///
 16/// # Examples
 17///
 18/// ```
 19/// use ui::{Avatar, AvatarShape};
 20///
 21/// Avatar::new("path/to/image.png")
 22///     .shape(AvatarShape::Circle)
 23///     .grayscale(true)
 24///     .border_color(gpui::red());
 25/// ```
 26#[derive(IntoElement)]
 27pub struct Avatar {
 28    image: Img,
 29    size: Option<Pixels>,
 30    border_color: Option<Hsla>,
 31    is_available: Option<bool>,
 32}
 33
 34impl RenderOnce for Avatar {
 35    fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
 36        if self.image.style().corner_radii.top_left.is_none() {
 37            self = self.shape(AvatarShape::Circle);
 38        }
 39
 40        let size = self.size.unwrap_or_else(|| cx.rem_size());
 41
 42        div()
 43            .size(size + px(2.))
 44            .map(|mut div| {
 45                div.style().corner_radii = self.image.style().corner_radii.clone();
 46                div
 47            })
 48            .when_some(self.border_color, |this, color| {
 49                this.border().border_color(color)
 50            })
 51            .child(
 52                self.image
 53                    .size(size)
 54                    .bg(cx.theme().colors().ghost_element_background),
 55            )
 56            .children(self.is_available.map(|is_free| {
 57                // HACK: non-integer sizes result in oval indicators.
 58                let indicator_size = (size * 0.4).round();
 59
 60                div()
 61                    .absolute()
 62                    .z_index(1)
 63                    .bg(if is_free {
 64                        cx.theme().status().created
 65                    } else {
 66                        cx.theme().status().deleted
 67                    })
 68                    .size(indicator_size)
 69                    .rounded(indicator_size)
 70                    .bottom_0()
 71                    .right_0()
 72            }))
 73    }
 74}
 75
 76impl Avatar {
 77    pub fn new(src: impl Into<ImageSource>) -> Self {
 78        Avatar {
 79            image: img(src),
 80            is_available: None,
 81            border_color: None,
 82            size: None,
 83        }
 84    }
 85
 86    /// Sets the shape of the avatar image.
 87    ///
 88    /// This method allows the shape of the avatar to be specified using a [`Shape`].
 89    /// It modifies the corner radius of the image to match the specified shape.
 90    ///
 91    /// # Examples
 92    ///
 93    /// ```
 94    /// use ui::{Avatar, AvatarShape};
 95    ///
 96    /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle);
 97    /// ```
 98    pub fn shape(mut self, shape: AvatarShape) -> Self {
 99        self.image = match shape {
100            AvatarShape::Circle => self.image.rounded_full(),
101            AvatarShape::RoundedRectangle => self.image.rounded_md(),
102        };
103        self
104    }
105
106    /// Applies a grayscale filter to the avatar image.
107    ///
108    /// # Examples
109    ///
110    /// ```
111    /// use ui::{Avatar, AvatarShape};
112    ///
113    /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
114    /// ```
115    pub fn grayscale(mut self, grayscale: bool) -> Self {
116        self.image = self.image.grayscale(grayscale);
117        self
118    }
119
120    pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
121        self.border_color = Some(color.into());
122        self
123    }
124
125    pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
126        self.is_available = is_available.into();
127        self
128    }
129
130    /// Size overrides the avatar size. By default they are 1rem.
131    pub fn size(mut self, size: impl Into<Option<Pixels>>) -> Self {
132        self.size = size.into();
133        self
134    }
135}