1use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
59use ferroui_base::data::core::ValueTypes;
60use ferroui_base::data::model::BindableList;
61use ferroui_base::{AnyValue, BoxedValue, PropertyValue};
62use std::any::Any;
63use std::rc::Rc;
64
65#[derive(Clone, Copy)]
70pub struct ItemsView<'a>(ItemsViewSource<'a>);
71
72#[derive(Clone, Copy)]
73enum ItemsViewSource<'a> {
74 Slice(&'a [Option<BoxedValue>]),
76 Window { list: &'a dyn IItemsList, start: usize, count: usize },
78 Typed(&'a dyn TypedItemsSource),
80}
81
82pub struct TypedItems<'a, T>(pub &'a [T]);
86
87trait TypedItemsSource {
88 fn len(&self) -> usize;
89 fn get(&self, index: usize) -> Option<BoxedValue>;
90}
91
92impl<T: PropertyValue> TypedItemsSource for TypedItems<'_, T> {
93 #[inline]
94 fn len(&self) -> usize {
95 self.0.len()
96 }
97
98 #[inline]
99 fn get(&self, index: usize) -> Option<BoxedValue> {
100 box_item(&self.0[index])
101 }
102}
103
104impl<'a> ItemsView<'a> {
105 pub const EMPTY: ItemsView<'static> = ItemsView(ItemsViewSource::Slice(&[]));
107
108 #[inline]
110 pub const fn from_slice(items: &'a [Option<BoxedValue>]) -> Self {
111 Self(ItemsViewSource::Slice(items))
112 }
113
114 #[inline]
117 pub fn window(list: &'a dyn IItemsList, start: usize, count: usize) -> Self {
118 Self(ItemsViewSource::Window { list, start, count })
119 }
120
121 #[inline]
123 pub fn of_source(source: &'a ItemsSource) -> Self {
124 Self::window(&**source.list(), 0, source.count())
125 }
126
127 #[inline]
129 pub fn of_source_range(source: &'a ItemsSource, start: usize, count: usize) -> Self {
130 Self::window(&**source.list(), start, count)
131 }
132
133 #[inline]
135 pub fn from_typed<T: PropertyValue>(items: &'a TypedItems<'a, T>) -> Self {
136 Self(ItemsViewSource::Typed(items))
137 }
138
139 #[inline]
141 pub fn len(&self) -> usize {
142 match self.0 {
143 ItemsViewSource::Slice(items) => items.len(),
144 ItemsViewSource::Window { count, .. } => count,
145 ItemsViewSource::Typed(items) => items.len(),
146 }
147 }
148
149 #[inline]
151 pub fn is_empty(&self) -> bool {
152 self.len() == 0
153 }
154
155 #[inline]
157 pub fn get(&self, index: usize) -> Option<BoxedValue> {
158 match self.0 {
159 ItemsViewSource::Slice(items) => items[index].clone(),
160 ItemsViewSource::Window { list, start, count } => {
161 assert!(index < count, "index out of range");
162 list.get_at(start + index)
163 }
164 ItemsViewSource::Typed(items) => items.get(index),
165 }
166 }
167
168 #[inline]
170 pub fn iter(&self) -> ItemsViewIter<'a> {
171 ItemsViewIter { view: *self, index: 0, end: self.len() }
172 }
173
174 pub fn to_vec(&self) -> Vec<Option<BoxedValue>> {
176 match self.0 {
177 ItemsViewSource::Slice(items) => items.to_vec(),
178 _ => self.iter().collect(),
179 }
180 }
181}
182
183impl Default for ItemsView<'_> {
184 fn default() -> Self {
185 ItemsView::EMPTY
186 }
187}
188
189impl std::fmt::Debug for ItemsView<'_> {
190 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 write!(f, "ItemsView(len = {})", self.len())
192 }
193}
194
195impl<'a> From<&'a [Option<BoxedValue>]> for ItemsView<'a> {
196 #[inline]
197 fn from(items: &'a [Option<BoxedValue>]) -> Self {
198 Self::from_slice(items)
199 }
200}
201
202impl<'a> From<&'a Vec<Option<BoxedValue>>> for ItemsView<'a> {
203 #[inline]
204 fn from(items: &'a Vec<Option<BoxedValue>>) -> Self {
205 Self::from_slice(items)
206 }
207}
208
209impl<'a, const N: usize> From<&'a [Option<BoxedValue>; N]> for ItemsView<'a> {
210 #[inline]
211 fn from(items: &'a [Option<BoxedValue>; N]) -> Self {
212 Self::from_slice(items)
213 }
214}
215
216impl<'a> From<&'a ItemsSource> for ItemsView<'a> {
217 #[inline]
218 fn from(source: &'a ItemsSource) -> Self {
219 Self::of_source(source)
220 }
221}
222
223#[derive(Clone)]
225pub struct ItemsViewIter<'a> {
226 view: ItemsView<'a>,
227 index: usize,
228 end: usize,
229}
230
231impl Iterator for ItemsViewIter<'_> {
232 type Item = Option<BoxedValue>;
233
234 #[inline]
235 fn next(&mut self) -> Option<Self::Item> {
236 if self.index < self.end {
237 self.index += 1;
238 Some(self.view.get(self.index - 1))
239 } else {
240 None
241 }
242 }
243
244 #[inline]
245 fn size_hint(&self) -> (usize, Option<usize>) {
246 let remaining = self.end - self.index;
247 (remaining, Some(remaining))
248 }
249}
250
251impl DoubleEndedIterator for ItemsViewIter<'_> {
252 #[inline]
253 fn next_back(&mut self) -> Option<Self::Item> {
254 if self.index < self.end {
255 self.end -= 1;
256 Some(self.view.get(self.end))
257 } else {
258 None
259 }
260 }
261}
262
263impl ExactSizeIterator for ItemsViewIter<'_> {}
264
265impl<'a> IntoIterator for ItemsView<'a> {
266 type Item = Option<BoxedValue>;
267 type IntoIter = ItemsViewIter<'a>;
268
269 #[inline]
270 fn into_iter(self) -> ItemsViewIter<'a> {
271 self.iter()
272 }
273}
274
275impl<'a> IntoIterator for &ItemsView<'a> {
276 type Item = Option<BoxedValue>;
277 type IntoIter = ItemsViewIter<'a>;
278
279 #[inline]
280 fn into_iter(self) -> ItemsViewIter<'a> {
281 self.iter()
282 }
283}
284
285#[derive(Clone, Copy, Debug)]
287pub struct ItemsChangedEventArgs<'a> {
288 pub action: NotifyCollectionChangedAction,
290 pub new_items: ItemsView<'a>,
292 pub old_items: ItemsView<'a>,
294 pub new_starting_index: i32,
296 pub old_starting_index: i32,
298}
299
300impl ItemsChangedEventArgs<'static> {
301 pub const RESET: ItemsChangedEventArgs<'static> = ItemsChangedEventArgs {
303 action: NotifyCollectionChangedAction::Reset,
304 new_items: ItemsView::EMPTY,
305 old_items: ItemsView::EMPTY,
306 new_starting_index: -1,
307 old_starting_index: -1,
308 };
309}
310
311impl<'a> ItemsChangedEventArgs<'a> {
312 #[inline]
314 pub fn add(new_items: impl Into<ItemsView<'a>>, new_starting_index: i32) -> Self {
315 Self {
316 action: NotifyCollectionChangedAction::Add,
317 new_items: new_items.into(),
318 old_items: ItemsView::EMPTY,
319 new_starting_index,
320 old_starting_index: -1,
321 }
322 }
323
324 #[inline]
326 pub fn remove(old_items: impl Into<ItemsView<'a>>, old_starting_index: i32) -> Self {
327 Self {
328 action: NotifyCollectionChangedAction::Remove,
329 new_items: ItemsView::EMPTY,
330 old_items: old_items.into(),
331 new_starting_index: -1,
332 old_starting_index,
333 }
334 }
335}
336
337impl<'a> From<&NotifyCollectionChangedEventArgs<'a, Option<BoxedValue>>> for ItemsChangedEventArgs<'a> {
340 #[inline]
341 fn from(e: &NotifyCollectionChangedEventArgs<'a, Option<BoxedValue>>) -> Self {
342 Self {
343 action: e.action,
344 new_items: ItemsView::from_slice(e.new_items),
345 old_items: ItemsView::from_slice(e.old_items),
346 new_starting_index: e.new_starting_index,
347 old_starting_index: e.old_starting_index,
348 }
349 }
350}
351
352pub type ItemsChangedHandler = dyn for<'a> Fn(&ItemsChangedEventArgs<'a>);
354
355#[inline]
357pub fn items_equal(a: &Option<BoxedValue>, b: &Option<BoxedValue>) -> bool {
358 match (a, b) {
359 (None, None) => true,
360 (Some(a), Some(b)) => Rc::ptr_eq(a, b) || (**a).any_value_eq(&**b),
361 _ => false,
362 }
363}
364
365pub fn reference_equals(a: &Option<BoxedValue>, b: &Option<BoxedValue>) -> bool {
384 match (a, b) {
385 (None, None) => true,
386 (Some(a), Some(b)) => boxed_reference_equals(a, b),
387 _ => false,
388 }
389}
390
391pub fn boxed_reference_equals(a: &BoxedValue, b: &BoxedValue) -> bool {
393 if std::ptr::addr_eq(Rc::as_ptr(a), Rc::as_ptr(b)) {
396 return true;
397 }
398
399 fn as_str(value: &dyn AnyValue) -> Option<&str> {
400 if let Some(text) = value.downcast_ref::<String>() {
401 return Some(text.as_str());
402 }
403 value.downcast_ref::<&'static str>().copied()
404 }
405
406 let (a, b): (&dyn AnyValue, &dyn AnyValue) = (&**a, &**b);
407 if let (Some(a), Some(b)) = (as_str(a), as_str(b)) {
408 return a == b;
409 }
410
411 match (ValueTypes::as_object(a), ValueTypes::as_object(b)) {
412 (Some(a), Some(b)) => a.ptr_eq(&b),
413 _ => false,
414 }
415}
416
417#[inline]
420pub fn box_item<T: PropertyValue>(value: &T) -> Option<BoxedValue> {
421 let any: &dyn Any = value;
422 if let Some(boxed) = any.downcast_ref::<BoxedValue>() {
423 return Some(boxed.clone());
424 }
425 if let Some(boxed) = any.downcast_ref::<Option<BoxedValue>>() {
426 return boxed.clone();
427 }
428 Some(Rc::new(value.clone()))
429}
430
431#[inline]
435pub fn unbox_item<T: PropertyValue>(item: &Option<BoxedValue>) -> Option<T> {
436 let boxed = item.as_ref()?;
437 let any: &dyn Any = boxed;
438 if let Some(value) = any.downcast_ref::<T>() {
439 return Some(value.clone());
441 }
442 let value: &dyn AnyValue = &**boxed;
443 value.downcast_ref::<T>().cloned()
444}
445
446#[inline]
448fn item_equals_typed<T: PropertyValue>(item: &Option<BoxedValue>, value: &T) -> bool {
449 let any: &dyn Any = value;
450 if let Some(boxed) = any.downcast_ref::<BoxedValue>() {
451 return item.as_ref().is_some_and(|item| Rc::ptr_eq(item, boxed) || (**item).any_value_eq(&**boxed));
452 }
453 if let Some(boxed) = any.downcast_ref::<Option<BoxedValue>>() {
454 return items_equal(item, boxed);
455 }
456 match item {
457 Some(item) => {
458 let item: &dyn AnyValue = &**item;
459 item.downcast_ref::<T>().is_some_and(|item| item == value)
460 }
461 None => false,
462 }
463}
464
465pub trait IItemsList {
467 fn count(&self) -> usize;
469
470 fn get_at(&self, index: usize) -> Option<BoxedValue>;
472
473 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
475 for i in 0..self.count() {
476 if items_equal(&self.get_at(i), item) {
477 return i as i32;
478 }
479 }
480 -1
481 }
482
483 fn is_notifying(&self) -> bool {
485 false
486 }
487
488 fn add_collection_changed(&self, _handler: Rc<ItemsChangedHandler>) -> Option<u64> {
492 None
493 }
494
495 fn remove_collection_changed(&self, _token: u64) {}
497
498 fn identity(&self) -> Option<usize> {
502 None
503 }
504
505 fn as_any(&self) -> &dyn Any;
507}
508
509impl IItemsList for Vec<Option<BoxedValue>> {
510 fn count(&self) -> usize {
511 self.len()
512 }
513
514 fn get_at(&self, index: usize) -> Option<BoxedValue> {
515 self[index].clone()
516 }
517
518 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
519 self.iter().position(|i| items_equal(i, item)).map_or(-1, |i| i as i32)
520 }
521
522 fn as_any(&self) -> &dyn Any {
523 self
524 }
525}
526
527impl<T: PropertyValue> IItemsList for FerroList<T> {
528 fn count(&self) -> usize {
529 FerroList::count(self)
530 }
531
532 fn get_at(&self, index: usize) -> Option<BoxedValue> {
533 let snapshot = self.snapshot();
534 box_item(&snapshot[index])
535 }
536
537 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
538 let snapshot = self.snapshot();
539 if let Some(index) = snapshot.iter().position(|i| item_equals_typed(item, i)) {
540 return index as i32;
541 }
542 let is_typed = item.as_ref().is_some_and(|boxed| (**boxed).downcast_ref::<T>().is_some());
546 if !is_typed {
547 if let Some(typed) = ferroui_base::metadata::from_markup_value::<T>(item) {
548 return snapshot.iter().position(|i| *i == typed).map_or(-1, |i| i as i32);
549 }
550 }
551 -1
552 }
553
554 fn is_notifying(&self) -> bool {
555 true
556 }
557
558 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
559 Some(FerroList::add_collection_changed(
560 self,
561 Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, T>| {
562 let new_items = TypedItems(e.new_items);
565 let old_items = TypedItems(e.old_items);
566 handler(&ItemsChangedEventArgs {
567 action: e.action,
568 new_items: ItemsView::from_typed(&new_items),
569 old_items: ItemsView::from_typed(&old_items),
570 new_starting_index: e.new_starting_index,
571 old_starting_index: e.old_starting_index,
572 });
573 }),
574 ))
575 }
576
577 fn remove_collection_changed(&self, token: u64) {
578 FerroList::remove_collection_changed(self, token);
579 }
580
581 fn identity(&self) -> Option<usize> {
582 Some(ferroui_base::utilities::WeakEventSender::sender_address(self))
583 }
584
585 fn as_any(&self) -> &dyn Any {
586 self
587 }
588}
589
590impl<T: PropertyValue> IItemsList for BindableList<T> {
591 fn count(&self) -> usize {
592 self.items().count()
593 }
594
595 fn get_at(&self, index: usize) -> Option<BoxedValue> {
596 IItemsList::get_at(self.items(), index)
597 }
598
599 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
600 IItemsList::index_of(self.items(), item)
601 }
602
603 fn is_notifying(&self) -> bool {
604 true
605 }
606
607 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
608 IItemsList::add_collection_changed(self.items(), handler)
609 }
610
611 fn remove_collection_changed(&self, token: u64) {
612 IItemsList::remove_collection_changed(self.items(), token);
613 }
614
615 fn as_any(&self) -> &dyn Any {
616 self
617 }
618}
619
620impl<T: PropertyValue> IItemsList for ferroui_system::collections::object_model::ObservableCollection<T> {
625 fn count(&self) -> usize {
626 ferroui_system::collections::object_model::ObservableCollection::count(self)
627 }
628
629 fn get_at(&self, index: usize) -> Option<BoxedValue> {
630 box_item(&self.get(index))
631 }
632
633 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
634 (0..ferroui_system::collections::object_model::ObservableCollection::count(self))
635 .position(|index| item_equals_typed(item, &self.get(index)))
636 .map_or(-1, |index| index as i32)
637 }
638
639 fn is_notifying(&self) -> bool {
640 true
641 }
642
643 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
644 Some(ferroui_system::collections::object_model::ObservableCollection::add_collection_changed(
645 self,
646 Rc::new(move |e: &ferroui_system::collections::specialized::NotifyCollectionChangedEventArgs<'_, T>| {
647 let new_items = TypedItems(e.new_items);
648 let old_items = TypedItems(e.old_items);
649 handler(&ItemsChangedEventArgs {
650 action: e.action,
651 new_items: ItemsView::from_typed(&new_items),
652 old_items: ItemsView::from_typed(&old_items),
653 new_starting_index: e.new_starting_index,
654 old_starting_index: e.old_starting_index,
655 });
656 }),
657 ))
658 }
659
660 fn remove_collection_changed(&self, token: u64) {
661 let _ = ferroui_system::collections::object_model::ObservableCollection::remove_collection_changed(self, token);
662 }
663
664 fn as_any(&self) -> &dyn Any {
665 self
666 }
667}
668
669#[derive(Clone)]
674pub struct ItemsSource(Rc<dyn IItemsList>);
675
676impl ItemsSource {
677 pub fn new(list: Rc<dyn IItemsList>) -> Self {
679 Self(list)
680 }
681
682 pub fn from_items(items: impl IntoIterator<Item = Option<BoxedValue>>) -> Self {
684 Self(Rc::new(items.into_iter().collect::<Vec<_>>()))
685 }
686
687 pub fn from_values<T: PropertyValue>(values: impl IntoIterator<Item = T>) -> Self {
689 Self(Rc::new(values.into_iter().map(|v| box_item(&v)).collect::<Vec<_>>()))
690 }
691
692 pub fn from_strs<'a>(values: impl IntoIterator<Item = &'a str>) -> Self {
694 Self::from_values(values.into_iter().map(str::to_string))
695 }
696
697 #[inline]
699 pub fn list(&self) -> &Rc<dyn IItemsList> {
700 &self.0
701 }
702
703 #[inline]
705 pub fn count(&self) -> usize {
706 self.0.count()
707 }
708
709 #[inline]
711 pub fn get_at(&self, index: usize) -> Option<BoxedValue> {
712 self.0.get_at(index)
713 }
714
715 #[inline]
717 pub fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
718 self.0.index_of(item)
719 }
720
721 #[inline]
723 pub fn contains(&self, item: &Option<BoxedValue>) -> bool {
724 self.0.index_of(item) >= 0
725 }
726
727 #[inline]
729 pub fn is_notifying(&self) -> bool {
730 self.0.is_notifying()
731 }
732
733 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
735 self.0.as_any().downcast_ref::<T>()
736 }
737
738 #[inline]
740 pub fn identity(&self) -> usize {
741 self.0.identity().unwrap_or(Rc::as_ptr(&self.0) as *const () as usize)
742 }
743
744 #[inline]
746 pub fn ptr_eq(&self, other: &ItemsSource) -> bool {
747 self.identity() == other.identity()
748 }
749
750 pub fn iter(&self) -> impl Iterator<Item = Option<BoxedValue>> + '_ {
752 let mut index = 0;
753 std::iter::from_fn(move || {
754 if index < self.0.count() {
755 index += 1;
756 Some(self.0.get_at(index - 1))
757 } else {
758 None
759 }
760 })
761 }
762
763 pub fn to_vec(&self) -> Vec<Option<BoxedValue>> {
765 self.iter().collect()
766 }
767
768 pub fn register_binding_conversion_with<S: 'static>(convert: fn(&S) -> ItemsSource) {
775 ValueTypes::register_conversion::<S, ItemsSource>(move |s| Some(convert(s)));
776 ValueTypes::register_conversion::<S, Option<ItemsSource>>(move |s| Some(Some(convert(s))));
777 }
778
779 pub fn register_binding_conversion<S: Clone + Into<ItemsSource> + 'static>() {
780 ValueTypes::register_conversion::<S, ItemsSource>(|s| Some(s.clone().into()));
781 ValueTypes::register_conversion::<S, Option<ItemsSource>>(|s| Some(Some(s.clone().into())));
782 }
783}
784
785impl PartialEq for ItemsSource {
786 fn eq(&self, other: &Self) -> bool {
787 self.ptr_eq(other)
788 }
789}
790
791impl std::fmt::Debug for ItemsSource {
792 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
793 write!(f, "ItemsSource(count = {})", self.count())
794 }
795}
796
797impl From<Rc<dyn IItemsList>> for ItemsSource {
798 fn from(value: Rc<dyn IItemsList>) -> Self {
799 Self(value)
800 }
801}
802
803impl<T: PropertyValue> From<Rc<FerroList<T>>> for ItemsSource {
804 fn from(value: Rc<FerroList<T>>) -> Self {
805 Self(value)
806 }
807}
808
809impl<T: PropertyValue> From<Rc<BindableList<T>>> for ItemsSource {
810 fn from(value: Rc<BindableList<T>>) -> Self {
811 Self(value)
812 }
813}
814
815impl From<Vec<Option<BoxedValue>>> for ItemsSource {
816 fn from(value: Vec<Option<BoxedValue>>) -> Self {
817 Self(Rc::new(value))
818 }
819}
820
821impl From<Vec<BoxedValue>> for ItemsSource {
822 fn from(value: Vec<BoxedValue>) -> Self {
823 Self::from_items(value.into_iter().map(Some))
824 }
825}
826
827impl From<Rc<Vec<Option<BoxedValue>>>> for ItemsSource {
828 fn from(value: Rc<Vec<Option<BoxedValue>>>) -> Self {
829 Self(value)
830 }
831}
832
833#[cfg(test)]
834mod reference_equals_tests {
835 use super::{boxed_reference_equals, items_equal, reference_equals};
836 use crate::{Border, Control};
837 use ferroui_base::{BoxedValue, Ref, StyledElement};
838 use std::rc::Rc;
839
840 fn boxed<T: PartialEq + 'static>(value: T) -> Option<BoxedValue> {
841 Some(Rc::new(value))
842 }
843
844 #[test]
845 fn nulls_and_the_same_box_are_the_same_reference() {
846 let value = boxed(42_i32);
847
848 assert!(reference_equals(&None, &None));
849 assert!(reference_equals(&value, &value.clone()));
850 assert!(!reference_equals(&value, &None));
851 assert!(!reference_equals(&None, &value));
852 }
853
854 #[test]
855 fn equal_strings_in_different_boxes_are_the_same_reference() {
856 assert!(reference_equals(&boxed("able".to_string()), &boxed("able".to_string())));
857 assert!(reference_equals(&boxed("able".to_string()), &boxed("able")));
858 assert!(reference_equals(&boxed(String::new()), &boxed(String::new())));
859 assert!(!reference_equals(&boxed("able".to_string()), &boxed("abide".to_string())));
860 assert!(!reference_equals(&boxed("able".to_string()), &boxed("Able".to_string())));
861 assert!(!reference_equals(&boxed("1".to_string()), &boxed(1_i32)));
862 }
863
864 #[test]
865 fn equal_values_in_different_boxes_are_different_references() {
866 assert!(!reference_equals(&boxed(42_i32), &boxed(42_i32)));
867 assert!(!reference_equals(&boxed(1.5_f64), &boxed(1.5_f64)));
868 assert!(!reference_equals(&boxed(true), &boxed(true)));
869 assert!(items_equal(&boxed(42_i32), &boxed(42_i32)));
871 }
872
873 #[test]
874 fn the_same_control_in_different_boxes_is_the_same_reference() {
875 let border = Border::new();
876 let as_control: Ref<Control> = border.clone().upcast();
877 let as_element: Ref<StyledElement> = border.clone().upcast();
878
879 assert!(reference_equals(&Some(Control::boxed(border.clone())), &Some(Control::boxed(border.clone()))));
880 assert!(reference_equals(&boxed(border.clone()), &boxed(as_control)));
882 assert!(reference_equals(&boxed(as_element), &boxed(border.clone())));
883 assert!(boxed_reference_equals(&Control::boxed(border.clone()), &Control::boxed(border)));
884 }
885
886 #[test]
887 fn different_controls_are_different_references() {
888 let (first, second) = (Border::new(), Border::new());
889
890 assert!(!reference_equals(&Some(Control::boxed(first.clone())), &Some(Control::boxed(second))));
891 assert!(!reference_equals(&Some(Control::boxed(first)), &boxed("able".to_string())));
892 }
893}
894
895#[cfg(test)]
896mod reference_item_tests {
897 use super::*;
900
901 #[derive(PartialEq)]
902 struct Model(i32);
903
904 #[test]
905 fn a_shared_object_is_found_in_a_list_of_its_handles() {
906 ValueTypes::register_reference::<Model>();
907 let (first, second) = (Rc::new(Model(1)), Rc::new(Model(2)));
908 let list: FerroList<Rc<Model>> = FerroList::new();
909 list.add(first.clone());
910 list.add(second.clone());
911 assert_eq!(1, IItemsList::index_of(&list, &IItemsList::get_at(&list, 1)));
913 assert_eq!(1, IItemsList::index_of(&list, &Some(second as BoxedValue)));
915 assert_eq!(-1, IItemsList::index_of(&list, &Some(Rc::new(7i32) as BoxedValue)));
916 }
917}
918
919#[cfg(test)]
920mod identity_tests {
921 use super::ItemsSource;
923 use ferroui_base::collections::FerroList;
924 use std::rc::Rc;
925
926 #[test]
927 fn two_handles_to_one_list_are_the_same_collection() {
928 let list = FerroList::from_items([1, 2, 3]);
929 let first = ItemsSource::from(Rc::new(list.clone()));
930 let second = ItemsSource::from(Rc::new(list.clone()));
931 assert!(first.ptr_eq(&second));
932 assert_eq!(first, second);
933
934 let other = ItemsSource::from(Rc::new(FerroList::from_items([1, 2, 3])));
935 assert!(!first.ptr_eq(&other));
936
937 let vector = ItemsSource::from(vec![None, None]);
939 assert!(vector.ptr_eq(&vector.clone()));
940 assert!(!vector.ptr_eq(&ItemsSource::from(vec![None, None])));
941 }
942}