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}