1use crate::{prelude::*, Indicator};
2
3use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
4
5/// An element that renders a user avatar with customizable appearance options.
6///
7/// # Examples
8///
9/// ```
10/// use ui::{Avatar, AvatarShape};
11///
12/// Avatar::new("path/to/image.png")
13/// .shape(AvatarShape::Circle)
14/// .grayscale(true)
15/// .border_color(gpui::red());
16/// ```
17#[derive(IntoElement, IntoComponent)]
18pub struct Avatar {
19 image: Img,
20 size: Option<AbsoluteLength>,
21 border_color: Option<Hsla>,
22 indicator: Option<AnyElement>,
23}
24
25impl Avatar {
26 /// Creates a new avatar element with the specified image source.
27 pub fn new(src: impl Into<ImageSource>) -> Self {
28 Avatar {
29 image: img(src),
30 size: None,
31 border_color: None,
32 indicator: None,
33 }
34 }
35
36 /// Applies a grayscale filter to the avatar image.
37 ///
38 /// # Examples
39 ///
40 /// ```
41 /// use ui::{Avatar, AvatarShape};
42 ///
43 /// let avatar = Avatar::new("path/to/image.png").grayscale(true);
44 /// ```
45 pub fn grayscale(mut self, grayscale: bool) -> Self {
46 self.image = self.image.grayscale(grayscale);
47 self
48 }
49
50 /// Sets the border color of the avatar.
51 ///
52 /// This might be used to match the border to the background color of
53 /// the parent element to create the illusion of cropping another
54 /// shape underneath (for example in face piles.)
55 pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
56 self.border_color = Some(color.into());
57 self
58 }
59
60 /// Size overrides the avatar size. By default they are 1rem.
61 pub fn size<L: Into<AbsoluteLength>>(mut self, size: impl Into<Option<L>>) -> Self {
62 self.size = size.into().map(Into::into);
63 self
64 }
65
66 /// Sets the current indicator to be displayed on the avatar, if any.
67 pub fn indicator<E: IntoElement>(mut self, indicator: impl Into<Option<E>>) -> Self {
68 self.indicator = indicator.into().map(IntoElement::into_any_element);
69 self
70 }
71}
72
73impl RenderOnce for Avatar {
74 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
75 let border_width = if self.border_color.is_some() {
76 px(2.)
77 } else {
78 px(0.)
79 };
80
81 let image_size = self.size.unwrap_or_else(|| rems(1.).into());
82 let container_size = image_size.to_pixels(window.rem_size()) + border_width * 2.;
83
84 div()
85 .size(container_size)
86 .rounded_full()
87 .when_some(self.border_color, |this, color| {
88 this.border(border_width).border_color(color)
89 })
90 .child(
91 self.image
92 .size(image_size)
93 .rounded_full()
94 .bg(cx.theme().colors().ghost_element_background),
95 )
96 .children(self.indicator.map(|indicator| div().child(indicator)))
97 }
98}
99
100// View this component preview using `workspace: open component-preview`
101impl ComponentPreview for Avatar {
102 fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
103 let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
104
105 v_flex()
106 .gap_6()
107 .children(vec![
108 example_group_with_title(
109 "Sizes",
110 vec![
111 single_example(
112 "Default",
113 Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
114 .into_any_element(),
115 ),
116 single_example(
117 "Small",
118 Avatar::new(example_avatar).size(px(24.)).into_any_element(),
119 ),
120 single_example(
121 "Large",
122 Avatar::new(example_avatar).size(px(48.)).into_any_element(),
123 ),
124 ],
125 ),
126 example_group_with_title(
127 "Styles",
128 vec![
129 single_example("Default", Avatar::new(example_avatar).into_any_element()),
130 single_example(
131 "Grayscale",
132 Avatar::new(example_avatar)
133 .grayscale(true)
134 .into_any_element(),
135 ),
136 single_example(
137 "With Border",
138 Avatar::new(example_avatar)
139 .border_color(gpui::red())
140 .into_any_element(),
141 ),
142 ],
143 ),
144 example_group_with_title(
145 "With Indicator",
146 vec![single_example(
147 "Dot",
148 Avatar::new(example_avatar)
149 .indicator(Indicator::dot().color(Color::Success))
150 .into_any_element(),
151 )],
152 ),
153 ])
154 .into_any_element()
155 }
156}