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/// Avatar::new("path/to/image.png")
 20///     .shape(AvatarShape::Circle)
 21///     .grayscale(true)
 22///     .border_color(cx.theme().colors().border)
 23/// ```
 24#[derive(IntoElement)]
 25pub struct Avatar {
 26    image: Img,
 27    border_color: Option<Hsla>,
 28    is_available: Option<bool>,
 29}
 30
 31impl RenderOnce for Avatar {
 32    fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
 33        if self.image.style().corner_radii.top_left.is_none() {
 34            self = self.shape(AvatarShape::Circle);
 35        }
 36
 37        let size = cx.rem_size();
 38
 39        div()
 40            .size(size + px(2.))
 41            .map(|mut div| {
 42                div.style().corner_radii = self.image.style().corner_radii.clone();
 43                div
 44            })
 45            .when_some(self.border_color, |this, color| {
 46                this.border().border_color(color)
 47            })
 48            .child(
 49                self.image
 50                    .size(size)
 51                    .bg(cx.theme().colors().ghost_element_background),
 52            )
 53            .children(self.is_available.map(|is_free| {
 54                // HACK: non-integer sizes result in oval indicators.
 55                let indicator_size = (size * 0.4).round();
 56
 57                div()
 58                    .absolute()
 59                    .z_index(1)
 60                    .bg(if is_free {
 61                        cx.theme().status().created
 62                    } else {
 63                        cx.theme().status().deleted
 64                    })
 65                    .size(indicator_size)
 66                    .rounded(indicator_size)
 67                    .bottom_0()
 68                    .right_0()
 69            }))
 70    }
 71}
 72
 73impl Avatar {
 74    pub fn new(src: impl Into<ImageSource>) -> Self {
 75        Avatar {
 76            image: img(src),
 77            is_available: None,
 78            border_color: None,
 79        }
 80    }
 81
 82    /// Sets the shape of the avatar image.
 83    ///
 84    /// This method allows the shape of the avatar to be specified using a [`Shape`].
 85    /// It modifies the corner radius of the image to match the specified shape.
 86    ///
 87    /// # Examples
 88    ///
 89    /// ```
 90    /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle);
 91    /// ```
 92    pub fn shape(mut self, shape: AvatarShape) -> Self {
 93        self.image = match shape {
 94            AvatarShape::Circle => self.image.rounded_full(),
 95            AvatarShape::RoundedRectangle => self.image.rounded_md(),
 96        };
 97        self
 98    }
 99
100    /// Applies a grayscale filter to the avatar image.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
106    /// ```
107    pub fn grayscale(mut self, grayscale: bool) -> Self {
108        self.image = self.image.grayscale(grayscale);
109        self
110    }
111
112    pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
113        self.border_color = Some(color.into());
114        self
115    }
116
117    pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
118        self.is_available = is_available.into();
119        self
120    }
121}