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}