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}