ferroui_base/media/
media_collection.rs1use crate::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
11use crate::reactive::{Disposable, IDisposable};
12use crate::{FerroObject, ObjectType, Ref};
13use std::cell::RefCell;
14use std::fmt;
15use std::rc::Rc;
16
17pub use crate::collections::ResetBehavior;
18
19pub struct MediaCollection<T: Clone + 'static> {
22 list: Rc<FerroList<T>>,
23}
24
25impl<T: Clone + 'static> Clone for MediaCollection<T> {
26 #[inline]
27 fn clone(&self) -> Self {
28 Self { list: self.list.clone() }
29 }
30}
31
32impl<T: Clone + 'static> PartialEq for MediaCollection<T> {
33 #[inline]
34 fn eq(&self, other: &Self) -> bool {
35 Rc::ptr_eq(&self.list, &other.list)
36 }
37}
38
39impl<T: Clone + 'static> Default for MediaCollection<T> {
40 fn default() -> Self {
41 Self::new()
42 }
43}
44
45impl<T: Clone + fmt::Debug + 'static> fmt::Debug for MediaCollection<T> {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_list().entries(self.list.snapshot().iter()).finish()
48 }
49}
50
51impl<T: Clone + 'static> FromIterator<T> for MediaCollection<T> {
52 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
53 Self::from_items(iter)
54 }
55}
56
57impl<T: Clone + 'static> MediaCollection<T> {
58 pub fn new() -> Self {
60 Self { list: Rc::new(FerroList::new()) }
61 }
62
63 pub fn from_items(items: impl IntoIterator<Item = T>) -> Self {
65 Self { list: Rc::new(FerroList::from_items(items)) }
66 }
67
68 pub fn from_list(list: Rc<FerroList<T>>) -> Self {
70 Self { list }
71 }
72
73 #[inline]
75 pub fn list(&self) -> &Rc<FerroList<T>> {
76 &self.list
77 }
78
79 #[inline]
81 pub fn ptr_eq(&self, other: &Self) -> bool {
82 Rc::ptr_eq(&self.list, &other.list)
83 }
84
85 #[inline]
87 pub fn reset_behavior(&self) -> ResetBehavior {
88 self.list.reset_behavior()
89 }
90
91 #[inline]
93 pub fn set_reset_behavior(&self, value: ResetBehavior) {
94 self.list.set_reset_behavior(value)
95 }
96
97 #[inline]
99 pub fn len(&self) -> usize {
100 self.list.count()
101 }
102
103 #[inline]
104 pub fn is_empty(&self) -> bool {
105 self.list.is_empty()
106 }
107
108 #[inline]
110 pub fn get(&self, index: usize) -> T {
111 self.list.get(index)
112 }
113
114 #[inline]
116 pub fn to_vec(&self) -> Vec<T> {
117 self.list.to_vec()
118 }
119
120 #[inline]
122 pub fn iter(&self) -> std::vec::IntoIter<T> {
123 self.to_vec().into_iter()
124 }
125
126 #[inline]
128 pub fn add(&self, item: T) {
129 self.list.add(item)
130 }
131
132 #[inline]
134 pub fn add_range(&self, items: impl IntoIterator<Item = T>) {
135 self.list.add_range(items)
136 }
137
138 #[inline]
140 pub fn insert(&self, index: usize, item: T) {
141 self.list.insert(index, item)
142 }
143
144 #[inline]
146 pub fn remove_at(&self, index: usize) {
147 self.list.remove_at(index)
148 }
149
150 #[inline]
152 pub fn clear(&self) {
153 self.list.clear()
154 }
155
156 pub fn collection_changed(
159 &self,
160 handler: impl for<'a, 'b> Fn(&'a NotifyCollectionChangedEventArgs<'b, T>) + 'static,
161 ) -> Rc<dyn IDisposable> {
162 let token = self.list.add_collection_changed(Rc::new(handler));
163 let weak = Rc::downgrade(&self.list);
164 Disposable::create(move || {
165 if let Some(list) = weak.upgrade() {
166 list.remove_collection_changed(token);
167 }
168 })
169 }
170
171 pub fn for_each_item(
175 &self,
176 added: impl Fn(usize, &T) + 'static,
177 removed: impl Fn(usize, &T) + 'static,
178 reset: impl Fn() + 'static,
179 ) -> Rc<dyn IDisposable> {
180 let add = move |index: usize, items: &[T]| {
181 for (i, item) in items.iter().enumerate() {
182 added(index + i, item);
183 }
184 };
185 let remove = move |index: usize, items: &[T]| {
186 for (i, item) in items.iter().enumerate().rev() {
187 removed(index + i, item);
188 }
189 };
190
191 add(0, &self.list.snapshot());
192
193 self.collection_changed(move |e| match e.action {
194 NotifyCollectionChangedAction::Add => add(e.new_starting_index as usize, e.new_items),
195 NotifyCollectionChangedAction::Move => {
196 if e.old_starting_index < 0 {
197 return reset();
198 }
199
200 remove(e.old_starting_index as usize, e.old_items);
201 let mut new_index = e.new_starting_index;
202
203 if new_index > e.old_starting_index {
204 new_index -= e.old_items.len() as i32 - 1;
205 }
206
207 add(new_index as usize, e.new_items);
208 }
209 NotifyCollectionChangedAction::Replace => {
210 if e.old_starting_index < 0 {
211 return reset();
212 }
213
214 remove(e.old_starting_index as usize, e.old_items);
215 add(e.new_starting_index as usize, e.new_items);
216 }
217 NotifyCollectionChangedAction::Remove => remove(e.old_starting_index as usize, e.old_items),
218 NotifyCollectionChangedAction::Reset => reset(),
219 })
220 }
221}
222
223impl<T: Clone + PartialEq + 'static> MediaCollection<T> {
224 #[inline]
226 pub fn set(&self, index: usize, value: T) {
227 self.list.set(index, value)
228 }
229
230 #[inline]
232 pub fn contains(&self, item: &T) -> bool {
233 self.list.contains(item)
234 }
235
236 #[inline]
238 pub fn index_of(&self, item: &T) -> Option<usize> {
239 self.list.index_of(item)
240 }
241
242 #[inline]
244 pub fn remove(&self, item: &T) -> bool {
245 self.list.remove(item)
246 }
247}
248
249pub fn track_item_property_changed<T: ObjectType>(
253 collection: &MediaCollection<Ref<T>>,
254 callback: impl Fn() + 'static,
255) -> Rc<dyn IDisposable> {
256 type Tracked = Rc<RefCell<Vec<(Ref<FerroObject>, Rc<dyn IDisposable>)>>>;
257 let tracked: Tracked = Rc::new(RefCell::new(Vec::new()));
258 let callback: Rc<dyn Fn()> = Rc::new(callback);
259
260 let tracked_add = tracked.clone();
261 let tracked_remove = tracked.clone();
262 let _ = collection.for_each_item(
265 move |_, item: &Ref<T>| {
266 let callback = callback.clone();
267 let object: &FerroObject = (**item).upcast();
268 let subscription = object.property_changed(move |_| callback());
269 tracked_add.borrow_mut().push((object.to_ref(), subscription));
270 },
271 move |_, item: &Ref<T>| {
272 let object: &FerroObject = (**item).upcast();
273 let object = object.to_ref();
274 let position = tracked_remove.borrow().iter().position(|(o, _)| o.ptr_eq(&object));
275 if let Some(position) = position {
276 let (_, subscription) = tracked_remove.borrow_mut().remove(position);
277 subscription.dispose();
278 }
279 },
280 || panic!("Collection reset not supported."),
281 );
282
283 Disposable::create(move || {
284 let items = std::mem::take(&mut *tracked.borrow_mut());
285 for (_, subscription) in items {
286 subscription.dispose();
287 }
288 })
289}
290
291macro_rules! media_collection_type {
293 ($(#[$meta:meta])* $name:ident, $item:ty) => {
294 $(#[$meta])*
295 #[derive(Clone, PartialEq)]
296 pub struct $name($crate::media::MediaCollection<$item>);
297
298 impl ::std::ops::Deref for $name {
299 type Target = $crate::media::MediaCollection<$item>;
300 #[inline]
301 fn deref(&self) -> &Self::Target {
302 &self.0
303 }
304 }
305
306 impl ::std::default::Default for $name {
307 fn default() -> Self {
308 Self::new()
309 }
310 }
311
312 impl ::std::iter::FromIterator<$item> for $name {
313 fn from_iter<I: IntoIterator<Item = $item>>(iter: I) -> Self {
314 Self::from_items(iter)
315 }
316 }
317 };
318}
319
320pub(crate) use media_collection_type;