1use std::sync::Arc;
2
3use crate::prelude::*;
4use gpui::{img, rems, Div, ImageData, ImageSource, IntoElement, Styled};
5
6#[derive(Debug, Default, PartialEq, Clone)]
7pub enum Shape {
8 #[default]
9 Circle,
10 RoundedRectangle,
11}
12
13#[derive(IntoElement)]
14pub struct Avatar {
15 src: ImageSource,
16 is_available: Option<bool>,
17 shape: Shape,
18}
19
20impl RenderOnce for Avatar {
21 type Rendered = Div;
22
23 fn render(self, cx: &mut WindowContext) -> Self::Rendered {
24 let mut img = img();
25
26 if self.shape == Shape::Circle {
27 img = img.rounded_full();
28 } else {
29 img = img.rounded_md();
30 }
31
32 let size = rems(1.0);
33
34 div()
35 .size(size)
36 .child(
37 img.source(self.src.clone())
38 .size(size)
39 // todo!(Pull the avatar fallback background from the theme.)
40 .bg(gpui::red()),
41 )
42 .children(self.is_available.map(|is_free| {
43 // HACK: non-integer sizes result in oval indicators.
44 let indicator_size = (size.0 * cx.rem_size() * 0.4).round();
45
46 div()
47 .absolute()
48 .z_index(1)
49 .bg(if is_free { gpui::green() } else { gpui::red() })
50 .size(indicator_size)
51 .rounded(indicator_size)
52 .bottom_0()
53 .right_0()
54 }))
55 }
56}
57
58impl Avatar {
59 pub fn uri(src: impl Into<SharedString>) -> Self {
60 Self {
61 src: src.into().into(),
62 shape: Shape::Circle,
63 is_available: None,
64 }
65 }
66 pub fn data(src: Arc<ImageData>) -> Self {
67 Self {
68 src: src.into(),
69 shape: Shape::Circle,
70 is_available: None,
71 }
72 }
73
74 pub fn source(src: ImageSource) -> Self {
75 Self {
76 src,
77 shape: Shape::Circle,
78 is_available: None,
79 }
80 }
81 pub fn shape(mut self, shape: Shape) -> Self {
82 self.shape = shape;
83 self
84 }
85 pub fn availability_indicator(mut self, is_available: impl Into<Option<bool>>) -> Self {
86 self.is_available = is_available.into();
87 self
88 }
89}