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 ferroui_base::metadata::into_markup_value(value.clone())
422}
423
424#[inline]
428pub fn unbox_item<T: PropertyValue>(item: &Option<BoxedValue>) -> Option<T> {
429 ferroui_base::metadata::from_markup_value(item)
430}
431
432#[inline]
434fn item_equals_typed<T: PropertyValue>(item: &Option<BoxedValue>, value: &T) -> bool {
435 let any: &dyn Any = value;
436 if let Some(boxed) = any.downcast_ref::<BoxedValue>() {
437 return item.as_ref().is_some_and(|item| Rc::ptr_eq(item, boxed) || (**item).any_value_eq(&**boxed));
438 }
439 if let Some(boxed) = any.downcast_ref::<Option<BoxedValue>>() {
440 return items_equal(item, boxed);
441 }
442 if let Some(item) = item {
444 if let Some(item) = (**item).downcast_ref::<T>() {
445 return item == value;
446 }
447 }
448 ferroui_base::metadata::from_markup_value::<T>(item)
450 .is_some_and(|item| &item == value)
451}
452
453pub trait IItemsList {
455 fn key_index_mapping(&self) -> Option<&dyn crate::IKeyIndexMapping> {
459 None
460 }
461
462 fn count(&self) -> usize;
464
465 fn get_at(&self, index: usize) -> Option<BoxedValue>;
467
468 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
470 for i in 0..self.count() {
471 if items_equal(&self.get_at(i), item) {
472 return i as i32;
473 }
474 }
475 -1
476 }
477
478 fn is_notifying(&self) -> bool {
480 false
481 }
482
483 fn add_collection_changed(&self, _handler: Rc<ItemsChangedHandler>) -> Option<u64> {
487 None
488 }
489
490 fn remove_collection_changed(&self, _token: u64) {}
492
493 fn identity(&self) -> Option<usize> {
497 None
498 }
499
500 fn as_any(&self) -> &dyn Any;
502}
503
504impl IItemsList for Vec<Option<BoxedValue>> {
505 fn count(&self) -> usize {
506 self.len()
507 }
508
509 fn get_at(&self, index: usize) -> Option<BoxedValue> {
510 self[index].clone()
511 }
512
513 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
514 self.iter().position(|i| items_equal(i, item)).map_or(-1, |i| i as i32)
515 }
516
517 fn as_any(&self) -> &dyn Any {
518 self
519 }
520}
521
522impl<T: PropertyValue> IItemsList for FerroList<T> {
523 fn count(&self) -> usize {
524 FerroList::count(self)
525 }
526
527 fn get_at(&self, index: usize) -> Option<BoxedValue> {
528 let snapshot = self.snapshot();
529 box_item(&snapshot[index])
530 }
531
532 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
533 let snapshot = self.snapshot();
534 if let Some(index) = snapshot.iter().position(|i| item_equals_typed(item, i)) {
535 return index as i32;
536 }
537 let is_typed = item.as_ref().is_some_and(|boxed| (**boxed).downcast_ref::<T>().is_some());
541 if !is_typed {
542 if let Some(typed) = ferroui_base::metadata::from_markup_value::<T>(item) {
543 return snapshot.iter().position(|i| *i == typed).map_or(-1, |i| i as i32);
544 }
545 }
546 -1
547 }
548
549 fn is_notifying(&self) -> bool {
550 true
551 }
552
553 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
554 Some(FerroList::add_collection_changed(
555 self,
556 Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, T>| {
557 let new_items = TypedItems(e.new_items);
560 let old_items = TypedItems(e.old_items);
561 handler(&ItemsChangedEventArgs {
562 action: e.action,
563 new_items: ItemsView::from_typed(&new_items),
564 old_items: ItemsView::from_typed(&old_items),
565 new_starting_index: e.new_starting_index,
566 old_starting_index: e.old_starting_index,
567 });
568 }),
569 ))
570 }
571
572 fn remove_collection_changed(&self, token: u64) {
573 FerroList::remove_collection_changed(self, token);
574 }
575
576 fn identity(&self) -> Option<usize> {
577 Some(ferroui_base::utilities::WeakEventSender::sender_address(self))
578 }
579
580 fn as_any(&self) -> &dyn Any {
581 self
582 }
583}
584
585impl<T: PropertyValue> IItemsList for BindableList<T> {
586 fn count(&self) -> usize {
587 self.items().count()
588 }
589
590 fn get_at(&self, index: usize) -> Option<BoxedValue> {
591 IItemsList::get_at(self.items(), index)
592 }
593
594 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
595 IItemsList::index_of(self.items(), item)
596 }
597
598 fn is_notifying(&self) -> bool {
599 true
600 }
601
602 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
603 IItemsList::add_collection_changed(self.items(), handler)
604 }
605
606 fn remove_collection_changed(&self, token: u64) {
607 IItemsList::remove_collection_changed(self.items(), token);
608 }
609
610 fn as_any(&self) -> &dyn Any {
611 self
612 }
613}
614
615impl<T: PropertyValue> IItemsList for ferroui_system::collections::object_model::ObservableCollection<T> {
620 fn count(&self) -> usize {
621 ferroui_system::collections::object_model::ObservableCollection::count(self)
622 }
623
624 fn get_at(&self, index: usize) -> Option<BoxedValue> {
625 box_item(&self.get(index))
626 }
627
628 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
629 (0..ferroui_system::collections::object_model::ObservableCollection::count(self))
630 .position(|index| item_equals_typed(item, &self.get(index)))
631 .map_or(-1, |index| index as i32)
632 }
633
634 fn is_notifying(&self) -> bool {
635 true
636 }
637
638 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
639 Some(ferroui_system::collections::object_model::ObservableCollection::add_collection_changed(
640 self,
641 Rc::new(move |e: &ferroui_system::collections::specialized::NotifyCollectionChangedEventArgs<'_, T>| {
642 let new_items = TypedItems(e.new_items);
643 let old_items = TypedItems(e.old_items);
644 handler(&ItemsChangedEventArgs {
645 action: e.action,
646 new_items: ItemsView::from_typed(&new_items),
647 old_items: ItemsView::from_typed(&old_items),
648 new_starting_index: e.new_starting_index,
649 old_starting_index: e.old_starting_index,
650 });
651 }),
652 ))
653 }
654
655 fn remove_collection_changed(&self, token: u64) {
656 let _ = ferroui_system::collections::object_model::ObservableCollection::remove_collection_changed(self, token);
657 }
658
659 fn as_any(&self) -> &dyn Any {
660 self
661 }
662}
663
664#[derive(Clone)]
669pub struct ItemsSource(Rc<dyn IItemsList>);
670
671impl ItemsSource {
672 pub fn new(list: Rc<dyn IItemsList>) -> Self {
674 Self(list)
675 }
676
677 pub fn from_items(items: impl IntoIterator<Item = Option<BoxedValue>>) -> Self {
679 Self(Rc::new(items.into_iter().collect::<Vec<_>>()))
680 }
681
682 pub fn from_values<T: PropertyValue>(values: impl IntoIterator<Item = T>) -> Self {
684 Self(Rc::new(values.into_iter().map(|v| box_item(&v)).collect::<Vec<_>>()))
685 }
686
687 pub fn from_strs<'a>(values: impl IntoIterator<Item = &'a str>) -> Self {
689 Self::from_values(values.into_iter().map(str::to_string))
690 }
691
692 #[inline]
694 pub fn list(&self) -> &Rc<dyn IItemsList> {
695 &self.0
696 }
697
698 #[inline]
700 pub fn count(&self) -> usize {
701 self.0.count()
702 }
703
704 #[inline]
706 pub fn get_at(&self, index: usize) -> Option<BoxedValue> {
707 self.0.get_at(index)
708 }
709
710 #[inline]
712 pub fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
713 self.0.index_of(item)
714 }
715
716 #[inline]
718 pub fn contains(&self, item: &Option<BoxedValue>) -> bool {
719 self.0.index_of(item) >= 0
720 }
721
722 #[inline]
724 pub fn is_notifying(&self) -> bool {
725 self.0.is_notifying()
726 }
727
728 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
730 self.0.as_any().downcast_ref::<T>()
731 }
732
733 #[inline]
735 pub fn identity(&self) -> usize {
736 self.0.identity().unwrap_or(Rc::as_ptr(&self.0) as *const () as usize)
737 }
738
739 #[inline]
741 pub fn ptr_eq(&self, other: &ItemsSource) -> bool {
742 self.identity() == other.identity()
743 }
744
745 pub fn iter(&self) -> impl Iterator<Item = Option<BoxedValue>> + '_ {
747 let mut index = 0;
748 std::iter::from_fn(move || {
749 if index < self.0.count() {
750 index += 1;
751 Some(self.0.get_at(index - 1))
752 } else {
753 None
754 }
755 })
756 }
757
758 pub fn to_vec(&self) -> Vec<Option<BoxedValue>> {
760 self.iter().collect()
761 }
762
763 pub fn register_binding_conversion_with<S: 'static>(convert: fn(&S) -> ItemsSource) {
770 ValueTypes::register_conversion::<S, ItemsSource>(move |s| Some(convert(s)));
771 ValueTypes::register_conversion::<S, Option<ItemsSource>>(move |s| Some(Some(convert(s))));
772 }
773
774 pub fn register_binding_conversion<S: Clone + Into<ItemsSource> + 'static>() {
775 ValueTypes::register_conversion::<S, ItemsSource>(|s| Some(s.clone().into()));
776 ValueTypes::register_conversion::<S, Option<ItemsSource>>(|s| Some(Some(s.clone().into())));
777 }
778}
779
780impl PartialEq for ItemsSource {
781 fn eq(&self, other: &Self) -> bool {
782 self.ptr_eq(other)
783 }
784}
785
786impl std::fmt::Debug for ItemsSource {
787 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
788 write!(f, "ItemsSource(count = {})", self.count())
789 }
790}
791
792impl From<Rc<dyn IItemsList>> for ItemsSource {
793 fn from(value: Rc<dyn IItemsList>) -> Self {
794 Self(value)
795 }
796}
797
798impl From<Rc<crate::ItemsSourceView>> for ItemsSource {
800 fn from(value: Rc<crate::ItemsSourceView>) -> Self {
801 Self(value)
802 }
803}
804
805impl<T: PropertyValue> From<Rc<FerroList<T>>> for ItemsSource {
806 fn from(value: Rc<FerroList<T>>) -> Self {
807 Self(value)
808 }
809}
810
811impl<T: PropertyValue> From<Rc<BindableList<T>>> for ItemsSource {
812 fn from(value: Rc<BindableList<T>>) -> Self {
813 Self(value)
814 }
815}
816
817impl From<Vec<Option<BoxedValue>>> for ItemsSource {
818 fn from(value: Vec<Option<BoxedValue>>) -> Self {
819 Self(Rc::new(value))
820 }
821}
822
823impl From<Vec<BoxedValue>> for ItemsSource {
824 fn from(value: Vec<BoxedValue>) -> Self {
825 Self::from_items(value.into_iter().map(Some))
826 }
827}
828
829impl From<Rc<Vec<Option<BoxedValue>>>> for ItemsSource {
830 fn from(value: Rc<Vec<Option<BoxedValue>>>) -> Self {
831 Self(value)
832 }
833}
834
835#[cfg(test)]
836mod reference_equals_tests {
837 use super::{boxed_reference_equals, items_equal, reference_equals};
838 use crate::{Border, Control};
839 use ferroui_base::{BoxedValue, Ref, StyledElement};
840 use std::rc::Rc;
841
842 fn boxed<T: PartialEq + 'static>(value: T) -> Option<BoxedValue> {
843 Some(Rc::new(value))
844 }
845
846 #[test]
847 fn nulls_and_the_same_box_are_the_same_reference() {
848 let value = boxed(42_i32);
849
850 assert!(reference_equals(&None, &None));
851 assert!(reference_equals(&value, &value.clone()));
852 assert!(!reference_equals(&value, &None));
853 assert!(!reference_equals(&None, &value));
854 }
855
856 #[test]
857 fn equal_strings_in_different_boxes_are_the_same_reference() {
858 assert!(reference_equals(&boxed("able".to_string()), &boxed("able".to_string())));
859 assert!(reference_equals(&boxed("able".to_string()), &boxed("able")));
860 assert!(reference_equals(&boxed(String::new()), &boxed(String::new())));
861 assert!(!reference_equals(&boxed("able".to_string()), &boxed("abide".to_string())));
862 assert!(!reference_equals(&boxed("able".to_string()), &boxed("Able".to_string())));
863 assert!(!reference_equals(&boxed("1".to_string()), &boxed(1_i32)));
864 }
865
866 #[test]
867 fn equal_values_in_different_boxes_are_different_references() {
868 assert!(!reference_equals(&boxed(42_i32), &boxed(42_i32)));
869 assert!(!reference_equals(&boxed(1.5_f64), &boxed(1.5_f64)));
870 assert!(!reference_equals(&boxed(true), &boxed(true)));
871 assert!(items_equal(&boxed(42_i32), &boxed(42_i32)));
873 }
874
875 #[test]
876 fn the_same_control_in_different_boxes_is_the_same_reference() {
877 let border = Border::new();
878 let as_control: Ref<Control> = border.clone().upcast();
879 let as_element: Ref<StyledElement> = border.clone().upcast();
880
881 assert!(reference_equals(&Some(Control::boxed(border.clone())), &Some(Control::boxed(border.clone()))));
882 assert!(reference_equals(&boxed(border.clone()), &boxed(as_control)));
884 assert!(reference_equals(&boxed(as_element), &boxed(border.clone())));
885 assert!(boxed_reference_equals(&Control::boxed(border.clone()), &Control::boxed(border)));
886 }
887
888 #[test]
889 fn different_controls_are_different_references() {
890 let (first, second) = (Border::new(), Border::new());
891
892 assert!(!reference_equals(&Some(Control::boxed(first.clone())), &Some(Control::boxed(second))));
893 assert!(!reference_equals(&Some(Control::boxed(first)), &boxed("able".to_string())));
894 }
895}
896
897#[cfg(test)]
898mod reference_item_tests {
899 use super::*;
902
903 #[derive(PartialEq)]
904 struct Model(i32);
905
906 #[test]
907 fn a_shared_object_is_found_in_a_list_of_its_handles() {
908 ValueTypes::register_reference::<Model>();
909 let (first, second) = (Rc::new(Model(1)), Rc::new(Model(2)));
910 let list: FerroList<Rc<Model>> = FerroList::new();
911 list.add(first.clone());
912 list.add(second.clone());
913 assert_eq!(1, IItemsList::index_of(&list, &IItemsList::get_at(&list, 1)));
915 assert_eq!(1, IItemsList::index_of(&list, &Some(second as BoxedValue)));
917 assert_eq!(-1, IItemsList::index_of(&list, &Some(Rc::new(7i32) as BoxedValue)));
918 }
919}
920
921#[cfg(test)]
922mod identity_tests {
923 use super::ItemsSource;
925 use ferroui_base::collections::FerroList;
926 use std::rc::Rc;
927
928 #[test]
929 fn two_handles_to_one_list_are_the_same_collection() {
930 let list = FerroList::from_items([1, 2, 3]);
931 let first = ItemsSource::from(Rc::new(list.clone()));
932 let second = ItemsSource::from(Rc::new(list.clone()));
933 assert!(first.ptr_eq(&second));
934 assert_eq!(first, second);
935
936 let other = ItemsSource::from(Rc::new(FerroList::from_items([1, 2, 3])));
937 assert!(!first.ptr_eq(&other));
938
939 let vector = ItemsSource::from(vec![None, None]);
941 assert!(vector.ptr_eq(&vector.clone()));
942 assert!(!vector.ptr_eq(&ItemsSource::from(vec![None, None])));
943 }
944}
945
946#[cfg(test)]
947mod nullable_item_tests {
948 use super::*;
949 use ferroui_base::data::core::ValueTypes;
950
951 #[test]
952 fn nullable_items_box_as_native_null_or_value_and_unbox_to_the_same_typed_slot() {
953 ValueTypes::register_nullable::<i32>();
954 assert!(box_item(&None::<i32>).is_none());
955 let boxed = box_item(&Some(42_i32));
956 assert_eq!(boxed.as_ref().unwrap().downcast_ref::<i32>(), Some(&42));
957 assert_eq!(unbox_item::<Option<i32>>(&None), Some(None));
958 assert_eq!(unbox_item::<Option<i32>>(&boxed), Some(Some(42)));
959 assert!(item_equals_typed(&None, &None::<i32>));
960 assert!(item_equals_typed(&boxed, &Some(42_i32)));
961 assert!(!item_equals_typed(&boxed, &None::<i32>));
962 }
963
964 #[test]
965 fn nullable_typed_change_views_keep_null_slots_and_untyped_reference_identity() {
966 ValueTypes::register_nullable::<i32>();
967 let values = [None, Some(7_i32), None];
968 let typed = TypedItems(&values);
969 let view = ItemsView::from_typed(&typed);
970 assert_eq!(view.len(), 3);
971 assert!(view.get(0).is_none() && view.get(2).is_none());
972 assert_eq!(view.get(1).unwrap().downcast_ref::<i32>(), Some(&7));
973 let value: BoxedValue = Rc::new("original".to_owned());
974 assert!(Rc::ptr_eq(&box_item(&value).unwrap(), &value));
975 assert!(Rc::ptr_eq(&box_item(&Some(value.clone())).unwrap(), &value));
976 assert!(item_equals_typed(&Some(value.clone()), &Some(value)));
977 }
978}