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