Skip to repository content133 lines · 4.0 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:43:34.830Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
facepile.rs
1use crate::component_prelude::*;
2use crate::prelude::*;
3use gpui::{AnyElement, StyleRefinement};
4use smallvec::SmallVec;
5
6use super::Avatar;
7
8/// An element that displays a collection of (usually) faces stacked
9/// horizontally, with the left-most face on top, visually descending
10/// from left to right.
11///
12/// Facepiles are used to display a group of people or things,
13/// such as a list of participants in a collaboration session.
14///
15/// # Examples
16///
17/// ## Default
18///
19/// A default, horizontal facepile.
20///
21/// ```
22/// use gpui::IntoElement;
23/// use ui::{Avatar, Facepile, EXAMPLE_FACES};
24///
25/// let facepile = Facepile::new(
26/// EXAMPLE_FACES.iter().take(3).map(|&url|
27/// Avatar::new(url).into_any_element()).collect()
28/// );
29/// ```
30#[derive(IntoElement, Documented, RegisterComponent)]
31pub struct Facepile {
32 base: Div,
33 faces: SmallVec<[AnyElement; 2]>,
34}
35
36impl Facepile {
37 /// Creates a new empty facepile.
38 pub fn empty() -> Self {
39 Self::new(SmallVec::new())
40 }
41
42 /// Creates a new facepile with the given faces.
43 pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
44 Self { base: div(), faces }
45 }
46}
47
48impl ParentElement for Facepile {
49 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
50 self.faces.extend(elements);
51 }
52}
53
54// Style methods.
55impl Facepile {
56 fn style(&mut self) -> &mut StyleRefinement {
57 self.base.style()
58 }
59
60 gpui::padding_style_methods!({
61 visibility: pub
62 });
63}
64
65impl RenderOnce for Facepile {
66 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
67 // Lay the faces out in reverse so they overlap in the desired order (left to right, front to back)
68 self.base
69 .flex()
70 .flex_row_reverse()
71 .items_center()
72 .justify_start()
73 .children(
74 self.faces
75 .into_iter()
76 .enumerate()
77 .rev()
78 .map(|(ix, player)| div().when(ix > 0, |div| div.ml_neg_1()).child(player)),
79 )
80 }
81}
82
83pub const EXAMPLE_FACES: [&str; 6] = [
84 "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
85 "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
86 "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
87 "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
88 "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
89 "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
90];
91
92impl Component for Facepile {
93 fn scope() -> ComponentScope {
94 ComponentScope::Collaboration
95 }
96
97 fn description() -> &'static str {
98 "Displays a collection of avatars or initials in a compact format. \
99 Often used to represent active collaborators or a subset of contributors."
100 }
101
102 fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
103 v_flex()
104 .gap_6()
105 .children(vec![example_group_with_title(
106 "Facepile Examples",
107 vec![
108 single_example(
109 "Default",
110 Facepile::new(
111 EXAMPLE_FACES
112 .iter()
113 .map(|&url| Avatar::new(url).into_any_element())
114 .collect(),
115 )
116 .into_any_element(),
117 ),
118 single_example(
119 "Custom Size",
120 Facepile::new(
121 EXAMPLE_FACES
122 .iter()
123 .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
124 .collect(),
125 )
126 .into_any_element(),
127 ),
128 ],
129 )])
130 .into_any_element()
131 }
132}
133