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}