1use crate::prelude::*;
2
3use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
4
5/// The shape of an [`Avatar`].
6#[derive(Debug, Default, PartialEq, Clone)]
7pub enum AvatarShape {
8 /// The avatar is shown in a circle.
9 #[default]
10 Circle,
11 /// The avatar is shown in a rectangle with rounded corners.
12 RoundedRectangle,
13}
14
15/// An element that renders a user avatar with customizable appearance options.
16///
17/// # Examples
18///
19/// ```
20/// use ui::{Avatar, AvatarShape};
21///
22/// Avatar::new("path/to/image.png")
23/// .shape(AvatarShape::Circle)
24/// .grayscale(true)
25/// .border_color(gpui::red());
26/// ```
27#[derive(IntoElement)]
28pub struct Avatar {
29 image: Img,
30 size: Option<AbsoluteLength>,
31 border_color: Option<Hsla>,
32 indicator: Option<AnyElement>,
33}
34
35impl Avatar {
36 pub fn new(src: impl Into<ImageSource>) -> Self {
37 Avatar {
38 image: img(src),
39 size: None,
40 border_color: None,
41 indicator: None,
42 }
43 }
44
45 /// Sets the shape of the avatar image.
46 ///
47 /// This method allows the shape of the avatar to be specified using an [`AvatarShape`].
48 /// It modifies the corner radius of the image to match the specified shape.
49 ///
50 /// # Examples
51 ///
52 /// ```
53 /// use ui::{Avatar, AvatarShape};
54 ///
55 /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle);
56 /// ```
57 pub fn shape(mut self, shape: AvatarShape) -> Self {
58 self.image = match shape {
59 AvatarShape::Circle => self.image.rounded_full(),
60 AvatarShape::RoundedRectangle => self.image.rounded_md(),
61 };
62 self
63 }
64
65 /// Applies a grayscale filter to the avatar image.
66 ///
67 /// # Examples
68 ///
69 /// ```
70 /// use ui::{Avatar, AvatarShape};
71 ///
72 /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
73 /// ```
74 pub fn grayscale(mut self, grayscale: bool) -> Self {
75 self.image = self.image.grayscale(grayscale);
76 self
77 }
78
79 pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
80 self.border_color = Some(color.into());
81 self
82 }
83
84 /// Size overrides the avatar size. By default they are 1rem.
85 pub fn size<L: Into<AbsoluteLength>>(mut self, size: impl Into<Option<L>>) -> Self {
86 self.size = size.into().map(Into::into);
87 self
88 }
89
90 pub fn indicator<E: IntoElement>(mut self, indicator: impl Into<Option<E>>) -> Self {
91 self.indicator = indicator.into().map(IntoElement::into_any_element);
92 self
93 }
94}
95
96impl RenderOnce for Avatar {
97 fn render(mut self, cx: &mut WindowContext) -> impl IntoElement {
98 if self.image.style().corner_radii.top_left.is_none() {
99 self = self.shape(AvatarShape::Circle);
100 }
101
102 let border_width = if self.border_color.is_some() {
103 px(2.)
104 } else {
105 px(0.)
106 };
107
108 let image_size = self.size.unwrap_or_else(|| rems(1.).into());
109 let container_size = image_size.to_pixels(cx.rem_size()) + border_width * 2.;
110
111 div()
112 .size(container_size)
113 .map(|mut div| {
114 div.style().corner_radii = self.image.style().corner_radii.clone();
115 div
116 })
117 .when_some(self.border_color, |this, color| {
118 this.border(border_width).border_color(color)
119 })
120 .child(
121 self.image
122 .size(image_size)
123 .bg(cx.theme().colors().ghost_element_background),
124 )
125 .children(self.indicator.map(|indicator| div().child(indicator)))
126 }
127}