1use crate::{prelude::*, Avatar};
2use gpui::{AnyElement, StyleRefinement};
3use smallvec::SmallVec;
4
5/// A facepile is a collection of faces stacked horizontally–
6/// always with the leftmost face on top and descending in z-index
7///
8/// Facepiles are used to display a group of people or things,
9/// such as a list of participants in a collaboration session.
10#[derive(IntoElement)]
11pub struct Facepile {
12 base: Div,
13 faces: SmallVec<[AnyElement; 2]>,
14}
15
16impl Facepile {
17 /// Creates a new empty facepile.
18 pub fn empty() -> Self {
19 Self::new(SmallVec::new())
20 }
21
22 /// Creates a new facepile with the given faces.
23 pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
24 Self { base: div(), faces }
25 }
26}
27
28impl ParentElement for Facepile {
29 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
30 self.faces.extend(elements);
31 }
32}
33
34// Style methods.
35impl Facepile {
36 fn style(&mut self) -> &mut StyleRefinement {
37 self.base.style()
38 }
39
40 gpui::padding_style_methods!({
41 visibility: pub
42 });
43}
44
45impl RenderOnce for Facepile {
46 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
47 // Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
48 self.base
49 .flex()
50 .flex_row_reverse()
51 .items_center()
52 .justify_start()
53 .children(
54 self.faces
55 .into_iter()
56 .enumerate()
57 .rev()
58 .map(|(ix, player)| div().when(ix > 0, |div| div.ml_neg_1()).child(player)),
59 )
60 }
61}
62
63impl ComponentPreview for Facepile {
64 fn description() -> impl Into<Option<&'static str>> {
65 "A facepile is a collection of faces stacked horizontally–\
66 always with the leftmost face on top and descending in z-index.\
67 \n\nFacepiles are used to display a group of people or things,\
68 such as a list of participants in a collaboration session."
69 }
70 fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
71 let few_faces: [&'static str; 3] = [
72 "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
73 "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
74 "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
75 ];
76
77 let many_faces: [&'static str; 6] = [
78 "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
79 "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
80 "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
81 "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
82 "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
83 "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
84 ];
85
86 vec![example_group_with_title(
87 "Examples",
88 vec![
89 single_example(
90 "Few Faces",
91 Facepile::new(
92 few_faces
93 .iter()
94 .map(|&url| Avatar::new(url).into_any_element())
95 .collect(),
96 ),
97 ),
98 single_example(
99 "Many Faces",
100 Facepile::new(
101 many_faces
102 .iter()
103 .map(|&url| Avatar::new(url).into_any_element())
104 .collect(),
105 ),
106 ),
107 single_example(
108 "Custom Size",
109 Facepile::new(
110 few_faces
111 .iter()
112 .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
113 .collect(),
114 ),
115 ),
116 ],
117 )]
118 }
119}