vizia_core/views/
avatar.rs1use vizia_storage::LayoutChildIterator;
2
3use crate::prelude::*;
4
5#[derive(Debug, Default, Clone, Copy, PartialEq)]
7pub enum AvatarVariant {
8 #[default]
9 Circle,
11 Square,
13 Rounded,
15}
16
17pub struct Avatar {}
29
30impl Avatar {
31 pub fn new<F>(cx: &mut Context, content: F) -> Handle<Self>
42 where
43 F: FnOnce(&mut Context),
44 {
45 Self {}.build(cx, content).variant(AvatarVariant::Circle).control_size(ControlSize::Medium)
46 }
47}
48
49impl View for Avatar {
50 fn element(&self) -> Option<&'static str> {
51 Some("avatar")
52 }
53}
54
55pub trait AvatarModifiers: Sized {
57 fn variant<U: Into<AvatarVariant> + Clone + PartialEq + 'static>(
69 self,
70 variant: impl Res<U> + 'static,
71 ) -> Self;
72
73 #[allow(unused_variables)]
85 fn badge<F>(self, content: F) -> Self
86 where
87 F: FnOnce(&mut Context) -> Handle<'_, Badge>,
88 {
89 self
90 }
91}
92
93impl AvatarModifiers for Handle<'_, Avatar> {
94 fn variant<U: Into<AvatarVariant> + Clone + PartialEq + 'static>(
95 mut self,
96 variant: impl Res<U> + 'static,
97 ) -> Self {
98 let avatar_variant = variant.to_signal(self.context()).map(|value| value.clone().into());
99
100 let is_circle = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Circle);
101
102 let is_square = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Square);
103
104 let is_rounded = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Rounded);
105
106 self.toggle_class("circle", is_circle)
107 .toggle_class("square", is_square)
108 .toggle_class("rounded", is_rounded)
109 }
110
111 fn badge<F>(mut self, content: F) -> Self
112 where
113 F: FnOnce(&mut Context) -> Handle<'_, Badge>,
114 {
115 let entity = self.entity();
116
117 self.context().with_current(entity, |cx| {
118 (content)(cx);
119 });
120
121 self
122 }
123}
124
125impl ControlModifiers for Handle<'_, Avatar> {
126 fn control_size<U: Into<ControlSize> + Clone + 'static>(
127 self,
128 size: impl Res<U> + 'static,
129 ) -> Self {
130 crate::modifiers::bind_control_size(self, size)
131 }
132}
133
134pub struct AvatarGroup {}
136
137impl AvatarGroup {
138 pub fn new<F>(cx: &mut Context, content: F) -> Handle<Self>
140 where
141 F: FnOnce(&mut Context),
142 {
143 Self {}.build(cx, content)
144 }
145}
146
147fn apply_avatar_group_max_visible(cx: &mut Context, entity: Entity, max_visible: usize) {
148 let mut avatars = Vec::new();
149 let mut overflow_avatar = None;
150
151 for child in LayoutChildIterator::new(&cx.tree, entity) {
152 let is_overflow = cx
153 .style
154 .classes
155 .get(child)
156 .is_some_and(|class_list| class_list.contains("avatar-group-overflow"));
157
158 if is_overflow {
159 overflow_avatar = Some(child);
160 } else {
161 avatars.push(child);
162 }
163 }
164
165 if let Some(overflow_avatar) = overflow_avatar {
166 cx.remove(overflow_avatar);
167 }
168
169 let hidden_count = avatars.len().saturating_sub(max_visible);
170
171 for (index, avatar) in avatars.into_iter().enumerate() {
172 let display = if index < max_visible { Display::Flex } else { Display::None };
173
174 Handle::<Avatar> { current: entity, entity: avatar, p: Default::default(), cx }
175 .display(display);
176 }
177
178 if hidden_count > 0 {
179 cx.with_current(entity, |cx| {
180 Avatar::new(cx, move |cx| {
181 Label::new(cx, format!("+{}", hidden_count));
182 })
183 .class("avatar-group-overflow");
184 });
185 }
186}
187
188impl View for AvatarGroup {
189 fn element(&self) -> Option<&'static str> {
190 Some("avatar-group")
191 }
192}
193
194impl AvatarModifiers for Handle<'_, AvatarGroup> {
195 fn variant<U: Into<AvatarVariant> + Clone + PartialEq + 'static>(
196 mut self,
197 variant: impl Res<U> + 'static,
198 ) -> Self {
199 let avatar_variant = variant.to_signal(self.context()).map(|value| value.clone().into());
200
201 let is_circle = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Circle);
202
203 let is_square = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Square);
204
205 let is_rounded = Memo::new(move |_| avatar_variant.get() == AvatarVariant::Rounded);
206
207 self.toggle_class("circle", is_circle)
208 .toggle_class("square", is_square)
209 .toggle_class("rounded", is_rounded)
210 }
211}
212
213impl ControlModifiers for Handle<'_, AvatarGroup> {
214 fn control_size<U: Into<ControlSize> + Clone + 'static>(
215 self,
216 size: impl Res<U> + 'static,
217 ) -> Self {
218 crate::modifiers::bind_control_size(self, size)
219 }
220}
221
222impl Handle<'_, AvatarGroup> {
223 pub fn max_visible(self, max_visible: impl Res<usize> + 'static) -> Self {
226 let max_visible = max_visible.to_signal(self.cx);
227 self.bind(max_visible, move |mut handle| {
228 let max_visible = max_visible.get();
229 let entity = handle.entity();
230 apply_avatar_group_max_visible(handle.context(), entity, max_visible);
231 })
232 }
233}