avatar.rs

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