// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! This module contains adapter models. use super::*; #[cfg(test)] #[derive(Default)] struct TestView { // Track the parameters reported by the model (row counts, indices, etc.). // The last field in the tuple is the row size the model reports at the time // of callback changed_rows: RefCell>, added_rows: RefCell>, removed_rows: RefCell>, reset: RefCell, } #[cfg(test)] impl TestView { fn clear(&self) { self.changed_rows.borrow_mut().clear(); self.added_rows.borrow_mut().clear(); self.removed_rows.borrow_mut().clear(); } } #[cfg(test)] impl ModelChangeListener for TestView { fn row_changed(self: Pin<&Self>, row: usize) { self.changed_rows.borrow_mut().push(row); } fn row_added(self: Pin<&Self>, index: usize, count: usize) { self.added_rows.borrow_mut().push((index, count)); } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { self.removed_rows.borrow_mut().push((index, count)); } fn reset(self: Pin<&Self>) { *self.reset.borrow_mut() += 1; } } #[cfg(test)] struct ModelChecker { model: Rc>, rows_copy: RefCell>, } #[cfg(test)] impl ModelChangeListener for ModelChecker { fn row_changed(self: Pin<&Self>, row: usize) { self.rows_copy.borrow_mut()[row] = self.model.row_data(row).unwrap(); } fn row_added(self: Pin<&Self>, index: usize, count: usize) { let mut copy = self.rows_copy.borrow_mut(); for row in index..index + count { copy.insert(row, self.model.row_data(row).unwrap()); } } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { self.rows_copy.borrow_mut().drain(index..index + count); } fn reset(self: Pin<&Self>) { *self.rows_copy.borrow_mut() = ModelRc::from(self.model.clone()).iter().collect() } } #[cfg(test)] impl ModelChecker { pub fn new( model: Rc + 'static>, ) -> Pin>> { let s = Self { rows_copy: RefCell::new(model.iter().collect()), model: model.clone() }; let s = Box::pin(ModelChangeListenerContainer::new(s)); model.model_tracker().attach_peer(s.as_ref().model_peer()); s } #[track_caller] pub fn check(&self) { assert_eq!( *self.rows_copy.borrow(), ModelRc::from(self.model.clone()).iter().collect::>() ); } } #[cfg(test)] impl Drop for ModelChecker { fn drop(&mut self) { self.check(); } } /// Provides rows that are generated by a map function based on the rows of another Model /// /// When the other Model is updated, the `MapModel` is updated accordingly. /// /// Generic parameters: /// * `M` the type of the wrapped `Model`. /// * `F` the map function. /// /// ## Example /// /// Here we have a [`VecModel`] holding rows of a custom type `Name`. /// It is then mapped into a `MapModel` of [`SharedString`]s /// /// ``` /// # use slint::{Model, VecModel, SharedString, MapModel}; /// #[derive(Clone)] /// struct Name { /// first: String, /// last: String, /// } /// /// let model = VecModel::from(vec![ /// Name { first: "Hans".to_string(), last: "Emil".to_string() }, /// Name { first: "Max".to_string(), last: "Mustermann".to_string() }, /// Name { first: "Roman".to_string(), last: "Tisch".to_string() }, /// ]); /// /// let mapped_model = MapModel::new(model, |n| /// slint::format!("{}, {}", n.last, n.first) /// ); /// /// assert_eq!(mapped_model.row_data(0).unwrap(), SharedString::from("Emil, Hans")); /// assert_eq!(mapped_model.row_data(1).unwrap(), SharedString::from("Mustermann, Max")); /// assert_eq!(mapped_model.row_data(2).unwrap(), SharedString::from("Tisch, Roman")); /// /// ``` /// /// Alternatively you can use the shortcut [`ModelExt::map`]. /// ``` /// # use slint::{Model, ModelExt, VecModel, SharedString, MapModel}; /// # #[derive(Clone)] /// # struct Name { /// # first: String, /// # last: String, /// # } /// let mapped_model = VecModel::from(vec![ /// Name { first: "Hans".to_string(), last: "Emil".to_string() }, /// Name { first: "Max".to_string(), last: "Mustermann".to_string() }, /// Name { first: "Roman".to_string(), last: "Tisch".to_string() }, /// ]) /// .map(|n| slint::format!("{}, {}", n.last, n.first)); /// # assert_eq!(mapped_model.row_data(0).unwrap(), SharedString::from("Emil, Hans")); /// # assert_eq!(mapped_model.row_data(1).unwrap(), SharedString::from("Mustermann, Max")); /// # assert_eq!(mapped_model.row_data(2).unwrap(), SharedString::from("Tisch, Roman")); /// ``` /// /// If you want to modify the underlying [`VecModel`] you can give it a [`Rc`] of the MapModel: /// ``` /// # use std::rc::Rc; /// # use slint::{Model, VecModel, SharedString, MapModel}; /// # #[derive(Clone)] /// # struct Name { /// # first: String, /// # last: String, /// # } /// let model = Rc::new(VecModel::from(vec![ /// Name { first: "Hans".to_string(), last: "Emil".to_string() }, /// Name { first: "Max".to_string(), last: "Mustermann".to_string() }, /// Name { first: "Roman".to_string(), last: "Tisch".to_string() }, /// ])); /// /// let mapped_model = MapModel::new(model.clone(), |n| /// slint::format!("{}, {}", n.last, n.first) /// ); /// /// model.set_row_data(1, Name { first: "Minnie".to_string(), last: "Musterfrau".to_string() }); /// /// assert_eq!(mapped_model.row_data(0).unwrap(), SharedString::from("Emil, Hans")); /// assert_eq!(mapped_model.row_data(1).unwrap(), SharedString::from("Musterfrau, Minnie")); /// assert_eq!(mapped_model.row_data(2).unwrap(), SharedString::from("Tisch, Roman")); /// /// ``` pub struct MapModel { wrapped_model: M, map_function: F, } impl Model for MapModel where M: 'static, F: 'static, F: Fn(T) -> U, M: Model, { type Data = U; fn row_count(&self) -> usize { self.wrapped_model.row_count() } fn row_data(&self, row: usize) -> Option { self.wrapped_model.row_data(row).map(|x| (self.map_function)(x)) } fn model_tracker(&self) -> &dyn ModelTracker { self.wrapped_model.model_tracker() } fn as_any(&self) -> &dyn core::any::Any { self } } impl MapModel where M: 'static, F: 'static, F: Fn(T) -> U, M: Model, { /// Creates a new MapModel based on the given `wrapped_model` and `map_function`. /// Alternatively you can use [`ModelExt::map`] on your Model. pub fn new(wrapped_model: M, map_function: F) -> Self { Self { wrapped_model, map_function } } /// Returns a reference to the inner model pub fn source_model(&self) -> &M { &self.wrapped_model } } #[test] fn test_map_model() { let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3])); let map = MapModel::new(wrapped_rc.clone(), |x| x.to_string()); wrapped_rc.set_row_data(2, 42); wrapped_rc.push(4); assert_eq!(map.row_data(2).unwrap(), "42"); assert_eq!(map.row_data(3).unwrap(), "4"); assert_eq!(map.row_data(1).unwrap(), "2"); } struct FilterModelInner where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static, { wrapped_model: M, filter_function: F, // This vector saves the indices of the elements that are not filtered out mapping: RefCell>, notify: ModelNotify, } impl FilterModelInner where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static, { fn build_mapping_vec(&self) { let mut mapping = self.mapping.borrow_mut(); *mapping = self .wrapped_model .iter() .enumerate() .filter_map(|(i, e)| (self.filter_function)(&e).then_some(i)) .collect(); } } impl ModelChangeListener for FilterModelInner where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static, { fn row_changed(self: Pin<&Self>, row: usize) { let mut mapping = self.mapping.borrow_mut(); let (index, is_contained) = match mapping.binary_search(&row) { Ok(index) => (index, true), Err(index) => (index, false), }; let should_be_contained = (self.filter_function)(&self.wrapped_model.row_data(row).unwrap()); if is_contained && should_be_contained { drop(mapping); self.notify.row_changed(index); } else if !is_contained && should_be_contained { mapping.insert(index, row); drop(mapping); self.notify.row_added(index, 1); } else if is_contained && !should_be_contained { mapping.remove(index); drop(mapping); self.notify.row_removed(index, 1); } } fn row_added(self: Pin<&Self>, index: usize, count: usize) { if count == 0 { return; } let insertion: Vec = self .wrapped_model .iter() .enumerate() .skip(index) .take(count) .filter_map(|(i, e)| (self.filter_function)(&e).then_some(i)) .collect(); let mut mapping = self.mapping.borrow_mut(); let insertion_point = mapping.binary_search(&index).unwrap_or_else(|ip| ip); mapping[insertion_point..].iter_mut().for_each(|i| *i += count); if !insertion.is_empty() { let insertion_len = insertion.len(); mapping.splice(insertion_point..insertion_point, insertion); drop(mapping); self.notify.row_added(insertion_point, insertion_len); } } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { if count == 0 { return; } let mut mapping = self.mapping.borrow_mut(); let start = mapping.binary_search(&index).unwrap_or_else(|s| s); let end = mapping.binary_search(&(index + count)).unwrap_or_else(|e| e); let range = start..end; mapping[end..].iter_mut().for_each(|i| *i -= count); if !range.is_empty() { mapping.drain(range.clone()); drop(mapping); self.notify.row_removed(start, range.len()); } } fn reset(self: Pin<&Self>) { self.build_mapping_vec(); self.notify.reset(); } } /// Provides a filtered subset of rows by another [`Model`]. /// /// When the other Model is updated, the `FilterModel` is updated accordingly. /// /// Generic parameters: /// * `M` the type of the wrapped `Model`. /// * `F` the filter function. /// /// ## Example /// /// Here we have a [`VecModel`] holding [`crate::SharedString`]s. /// It is then filtered into a `FilterModel`. /// /// ``` /// # use slint::{Model, VecModel, SharedString, FilterModel}; /// let model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]); /// /// let filtered_model = FilterModel::new(model, |s| s.contains('o')); /// /// assert_eq!(filtered_model.row_data(0).unwrap(), SharedString::from("Lorem")); /// assert_eq!(filtered_model.row_data(1).unwrap(), SharedString::from("dolor")); /// ``` /// /// Alternatively you can use the shortcut [`ModelExt::filter`]. /// ``` /// # use slint::{Model, ModelExt, VecModel, SharedString, FilterModel}; /// let filtered_model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]).filter(|s| s.contains('o')); /// # assert_eq!(filtered_model.row_data(0).unwrap(), SharedString::from("Lorem")); /// # assert_eq!(filtered_model.row_data(1).unwrap(), SharedString::from("dolor")); /// ``` /// /// If you want to modify the underlying [`VecModel`] you can give it a [`Rc`] of the FilterModel: /// ``` /// # use std::rc::Rc; /// # use slint::{Model, VecModel, SharedString, FilterModel}; /// let model = Rc::new(VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ])); /// /// let filtered_model = FilterModel::new(model.clone(), |s| s.contains('o')); /// /// assert_eq!(filtered_model.row_data(0).unwrap(), SharedString::from("Lorem")); /// assert_eq!(filtered_model.row_data(1).unwrap(), SharedString::from("dolor")); /// /// model.set_row_data(1, SharedString::from("opsom")); /// /// assert_eq!(filtered_model.row_data(0).unwrap(), SharedString::from("Lorem")); /// assert_eq!(filtered_model.row_data(1).unwrap(), SharedString::from("opsom")); /// assert_eq!(filtered_model.row_data(2).unwrap(), SharedString::from("dolor")); /// ``` pub struct FilterModel(Pin>>>) where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static; impl FilterModel where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static, { /// Creates a new FilterModel based on the given `wrapped_model` and filtered by `filter_function`. /// Alternatively you can use [`ModelExt::filter`] on your Model. pub fn new(wrapped_model: M, filter_function: F) -> Self { let filter_model_inner = FilterModelInner { wrapped_model, filter_function, mapping: RefCell::new(Vec::new()), notify: Default::default(), }; filter_model_inner.build_mapping_vec(); let container = Box::pin(ModelChangeListenerContainer::new(filter_model_inner)); container.wrapped_model.model_tracker().attach_peer(container.as_ref().model_peer()); Self(container) } /// Manually reapply the filter. You need to run this e.g. if the filtering function depends on /// mutable state and it has changed. pub fn reset(&self) { self.0.as_ref().get().reset(); } /// Gets the row index of the underlying unfiltered model for a given filtered row index. pub fn unfiltered_row(&self, filtered_row: usize) -> usize { self.0.mapping.borrow()[filtered_row] } /// Returns a reference to the inner model pub fn source_model(&self) -> &M { &self.0.as_ref().get().get_ref().wrapped_model } } impl Model for FilterModel where M: Model + 'static, F: Fn(&M::Data) -> bool + 'static, { type Data = M::Data; fn row_count(&self) -> usize { self.0.mapping.borrow().len() } fn row_data(&self, row: usize) -> Option { self.0 .mapping .borrow() .get(row) .and_then(|&wrapped_row| self.0.wrapped_model.row_data(wrapped_row)) } fn set_row_data(&self, row: usize, data: Self::Data) { let wrapped_row = self.0.mapping.borrow()[row]; self.0.wrapped_model.set_row_data(wrapped_row, data); } fn model_tracker(&self) -> &dyn ModelTracker { &self.0.notify } fn as_any(&self) -> &dyn core::any::Any { self } } #[test] fn test_filter_model() { let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4, 5, 6])); let filter = Rc::new(FilterModel::new(wrapped_rc.clone(), |x| x % 2 == 0)); let _checker = ModelChecker::new(filter.clone()); assert_eq!(filter.row_data(0).unwrap(), 2); assert_eq!(filter.row_data(1).unwrap(), 4); assert_eq!(filter.row_data(2).unwrap(), 6); assert_eq!(filter.row_count(), 3); wrapped_rc.remove(1); assert_eq!(filter.row_data(0).unwrap(), 4); assert_eq!(filter.row_data(1).unwrap(), 6); assert_eq!(filter.row_count(), 2); wrapped_rc.push(8); wrapped_rc.push(7); assert_eq!(filter.row_data(0).unwrap(), 4); assert_eq!(filter.row_data(1).unwrap(), 6); assert_eq!(filter.row_data(2).unwrap(), 8); assert_eq!(filter.row_count(), 3); wrapped_rc.set_row_data(1, 2); assert_eq!(filter.row_data(0).unwrap(), 2); assert_eq!(filter.row_data(1).unwrap(), 4); assert_eq!(filter.row_data(2).unwrap(), 6); assert_eq!(filter.row_data(3).unwrap(), 8); assert_eq!(filter.row_count(), 4); wrapped_rc.insert(2, 12); assert_eq!(filter.row_data(0).unwrap(), 2); assert_eq!(filter.row_data(1).unwrap(), 12); assert_eq!(filter.row_data(2).unwrap(), 4); assert_eq!(filter.row_data(3).unwrap(), 6); assert_eq!(filter.row_data(4).unwrap(), 8); assert_eq!(filter.row_count(), 5); } #[test] fn test_filter_model_source_model() { let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(FilterModel::new(wrapped_rc.clone(), |x| x % 2 == 0)); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); let _checker = ModelChecker::new(model.clone()); model.source_model().push(5); model.source_model().push(6); let expected = &[2, 4, 6]; assert_eq!(model.row_count(), expected.len()); for (i, v) in expected.iter().enumerate() { assert_eq!(model.row_data(i), Some(*v), "Expected {} at index {}", v, i); } } pub trait SortHelper { fn cmp(&mut self, lhs: &D, rhs: &D) -> core::cmp::Ordering; } pub struct AscendingSortHelper; impl SortHelper for AscendingSortHelper where D: core::cmp::Ord, { fn cmp(&mut self, lhs: &D, rhs: &D) -> core::cmp::Ordering { lhs.cmp(rhs) } } impl SortHelper for F where F: FnMut(&D, &D) -> core::cmp::Ordering + 'static, { fn cmp(&mut self, lhs: &D, rhs: &D) -> core::cmp::Ordering { (self)(lhs, rhs) } } struct SortModelInner where M: Model + 'static, S: SortHelper + 'static, { wrapped_model: M, sort_helper: RefCell, // This vector saves the indices of the elements in sorted order. mapping: RefCell>, notify: ModelNotify, sorted_rows_dirty: Cell, } impl SortModelInner where M: Model + 'static, S: SortHelper, { fn build_mapping_vec(&self) { if !self.sorted_rows_dirty.get() { return; } let mut mapping = self.mapping.borrow_mut(); mapping.clear(); mapping.extend(0..self.wrapped_model.row_count()); mapping.sort_by(|lhs, rhs| { self.sort_helper.borrow_mut().cmp( &self.wrapped_model.row_data(*lhs).unwrap(), &self.wrapped_model.row_data(*rhs).unwrap(), ) }); self.sorted_rows_dirty.set(false); } } impl ModelChangeListener for SortModelInner where M: Model + 'static, S: SortHelper + 'static, { fn row_changed(self: Pin<&Self>, row: usize) { if self.sorted_rows_dirty.get() { self.reset(); return; } let mut mapping = self.mapping.borrow_mut(); let removed_index = mapping.iter().position(|r| *r == row).unwrap(); mapping.remove(removed_index); let changed_data = self.wrapped_model.row_data(row).unwrap(); let insertion_index = mapping.partition_point(|existing_row| { self.sort_helper .borrow_mut() .cmp(&self.wrapped_model.row_data(*existing_row).unwrap(), &changed_data) == core::cmp::Ordering::Less }); mapping.insert(insertion_index, row); drop(mapping); if insertion_index == removed_index { self.notify.row_changed(removed_index); } else { self.notify.row_removed(removed_index, 1); self.notify.row_added(insertion_index, 1); } } fn row_added(self: Pin<&Self>, index: usize, count: usize) { if count == 0 { return; } if self.sorted_rows_dirty.get() { self.reset(); return; } // Adjust the existing sorted row indices to match the updated source model for row in self.mapping.borrow_mut().iter_mut() { if *row >= index { *row += count; } } for row in index..(index + count) { let added_data = self.wrapped_model.row_data(row).unwrap(); let insertion_index = self.mapping.borrow().partition_point(|existing_row| { self.sort_helper .borrow_mut() .cmp(&self.wrapped_model.row_data(*existing_row).unwrap(), &added_data) == core::cmp::Ordering::Less }); self.mapping.borrow_mut().insert(insertion_index, row); self.notify.row_added(insertion_index, 1) } } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { if count == 0 { return; } if self.sorted_rows_dirty.get() { self.reset(); return; } let mut removed_rows = Vec::new(); let mut i = 0; loop { if i >= self.mapping.borrow().len() { break; } let sort_index = self.mapping.borrow()[i]; if sort_index >= index { if sort_index < index + count { removed_rows.push(i); self.mapping.borrow_mut().remove(i); continue; } else { self.mapping.borrow_mut()[i] -= count; } } i += 1; } for removed_row in removed_rows { self.notify.row_removed(removed_row, 1); } } fn reset(self: Pin<&Self>) { self.sorted_rows_dirty.set(true); self.notify.reset(); } } /// Provides a sorted view of rows by another [`Model`]. /// /// When the other Model is updated, the `Sorted` is updated accordingly. /// /// Generic parameters: /// * `M` the type of the wrapped `Model`. /// * `F` a type that provides an order to model rows. It is constrained by the internal trait `SortHelper`, which is used to sort the model in ascending order if the model data supports it, or by a given sort function. /// /// ## Example /// /// Here we have a [`VecModel`] holding [`crate::SharedString`]s. /// It is then sorted into a `SortModel`. /// /// ``` /// # use slint::{Model, VecModel, SharedString, SortModel}; /// let model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]); /// /// let sorted_model = SortModel::new(model, |lhs, rhs| lhs.to_lowercase().cmp(&rhs.to_lowercase())); /// /// assert_eq!(sorted_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(sorted_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// assert_eq!(sorted_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// ``` /// /// Alternatively you can use the shortcut [`ModelExt::sort_by`]. /// ``` /// # use slint::{Model, ModelExt, VecModel, SharedString, SortModel}; /// let sorted_model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]).sort_by(|lhs, rhs| lhs.to_lowercase().cmp(&rhs.to_lowercase())); /// # assert_eq!(sorted_model.row_data(0).unwrap(), SharedString::from("dolor")); /// # assert_eq!(sorted_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// # assert_eq!(sorted_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// ``` /// /// It is also possible to get a ascending sorted `SortModel` order for `core::cmp::Ord` type items. /// /// ``` /// # use slint::{Model, VecModel, SortModel}; /// let model = VecModel::from(vec![ /// 5, /// 1, /// 3, /// ]); /// /// let sorted_model = SortModel::new_ascending(model); /// /// assert_eq!(sorted_model.row_data(0).unwrap(), 1); /// assert_eq!(sorted_model.row_data(1).unwrap(), 3); /// assert_eq!(sorted_model.row_data(2).unwrap(), 5); /// ``` /// /// Alternatively you can use the shortcut [`ModelExt::sort`]. /// ``` /// # use slint::{Model, ModelExt, VecModel, SharedString, SortModel}; /// let sorted_model = VecModel::from(vec![ /// 5, /// 1, /// 3, /// ]).sort(); /// # assert_eq!(sorted_model.row_data(0).unwrap(), 1); /// # assert_eq!(sorted_model.row_data(1).unwrap(), 3); /// # assert_eq!(sorted_model.row_data(2).unwrap(), 5); /// ``` /// /// If you want to modify the underlying [`VecModel`] you can give it a [`Rc`] of the SortModel: /// ``` /// # use std::rc::Rc; /// # use slint::{Model, VecModel, SharedString, SortModel}; /// let model = Rc::new(VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ])); /// /// let sorted_model = SortModel::new(model.clone(), |lhs, rhs| lhs.to_lowercase().cmp(&rhs.to_lowercase())); /// /// assert_eq!(sorted_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(sorted_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// assert_eq!(sorted_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// /// model.set_row_data(1, SharedString::from("opsom")); /// /// assert_eq!(sorted_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(sorted_model.row_data(1).unwrap(), SharedString::from("Lorem")); /// assert_eq!(sorted_model.row_data(2).unwrap(), SharedString::from("opsom")); /// ``` pub struct SortModel(Pin>>>) where M: Model + 'static, F: SortHelper + 'static; impl SortModel where M: Model + 'static, F: FnMut(&M::Data, &M::Data) -> core::cmp::Ordering + 'static, { /// Creates a new SortModel based on the given `wrapped_model` and sorted by `sort_function`. /// Alternatively you can use [`ModelExt::sort_by`] on your Model. pub fn new(wrapped_model: M, sort_function: F) -> Self where F: FnMut(&M::Data, &M::Data) -> core::cmp::Ordering + 'static, { let sorted_model_inner = SortModelInner { wrapped_model, sort_helper: RefCell::new(sort_function), mapping: RefCell::new(Vec::new()), notify: Default::default(), sorted_rows_dirty: Cell::new(true), }; let container = Box::pin(ModelChangeListenerContainer::new(sorted_model_inner)); container.wrapped_model.model_tracker().attach_peer(container.as_ref().model_peer()); Self(container) } /// Returns a reference to the inner model pub fn source_model(&self) -> &M { &self.0.as_ref().get().get_ref().wrapped_model } } impl SortModel where M: Model + 'static, M::Data: core::cmp::Ord, { /// Creates a new SortModel based on the given `wrapped_model` and sorted in ascending order. /// Alternatively you can use [`ModelExt::sort`] on your Model. pub fn new_ascending(wrapped_model: M) -> Self where M::Data: core::cmp::Ord, { let sorted_model_inner = SortModelInner { wrapped_model, sort_helper: RefCell::new(AscendingSortHelper), mapping: RefCell::new(Vec::new()), notify: Default::default(), sorted_rows_dirty: Cell::new(true), }; let container = Box::pin(ModelChangeListenerContainer::new(sorted_model_inner)); container.wrapped_model.model_tracker().attach_peer(container.as_ref().model_peer()); Self(container) } /// Manually reapply the sorting. You need to run this e.g. if the sort function depends /// on mutable state and it has changed. pub fn reset(&self) { self.0.as_ref().get().reset(); } /// Gets the row index of the underlying unsorted model for a given sorted row index. pub fn unsorted_row(&self, sorted_row: usize) -> usize { self.0.build_mapping_vec(); self.0.mapping.borrow()[sorted_row] } } impl Model for SortModel where M: Model + 'static, S: SortHelper, { type Data = M::Data; fn row_count(&self) -> usize { self.0.wrapped_model.row_count() } fn row_data(&self, row: usize) -> Option { self.0.build_mapping_vec(); self.0 .mapping .borrow() .get(row) .and_then(|&wrapped_row| self.0.wrapped_model.row_data(wrapped_row)) } fn set_row_data(&self, row: usize, data: Self::Data) { let wrapped_row = self.0.mapping.borrow()[row]; self.0.wrapped_model.set_row_data(wrapped_row, data); } fn model_tracker(&self) -> &dyn ModelTracker { &self.0.notify } fn as_any(&self) -> &dyn core::any::Any { self } } #[cfg(test)] mod sort_tests { use super::*; #[test] fn test_sorted_model_insert() { let wrapped_rc = Rc::new(VecModel::from(vec![3, 4, 1, 2])); let sorted_model = Rc::new(SortModel::new(wrapped_rc.clone(), |lhs, rhs| lhs.cmp(rhs))); let _checker = ModelChecker::new(sorted_model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); sorted_model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); assert_eq!(sorted_model.row_count(), 4); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); assert_eq!(sorted_model.row_data(3).unwrap(), 4); wrapped_rc.insert(0, 10); assert_eq!(observer.added_rows.borrow().len(), 1); assert!(observer.added_rows.borrow().eq(&[(4, 1)])); assert!(observer.changed_rows.borrow().is_empty()); assert!(observer.removed_rows.borrow().is_empty()); assert_eq!(*observer.reset.borrow(), 0); observer.clear(); assert_eq!(sorted_model.row_count(), 5); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); assert_eq!(sorted_model.row_data(3).unwrap(), 4); assert_eq!(sorted_model.row_data(4).unwrap(), 10); } #[test] fn test_sorted_model_remove() { let wrapped_rc = Rc::new(VecModel::from(vec![3, 4, 1, 2])); let sorted_model = Rc::new(SortModel::new(wrapped_rc.clone(), |lhs, rhs| lhs.cmp(rhs))); let _checker = ModelChecker::new(sorted_model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); sorted_model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); assert_eq!(sorted_model.row_count(), 4); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); assert_eq!(sorted_model.row_data(3).unwrap(), 4); // Remove the entry with the value 4 wrapped_rc.remove(1); assert!(observer.added_rows.borrow().is_empty()); assert!(observer.changed_rows.borrow().is_empty()); assert_eq!(observer.removed_rows.borrow().len(), 1); assert!(observer.removed_rows.borrow().eq(&[(3, 1)])); assert_eq!(*observer.reset.borrow(), 0); observer.clear(); assert_eq!(sorted_model.row_count(), 3); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); } #[test] fn test_sorted_model_changed() { let wrapped_rc = Rc::new(VecModel::from(vec![3, 4, 1, 2])); let sorted_model = Rc::new(SortModel::new(wrapped_rc.clone(), |lhs, rhs| lhs.cmp(rhs))); let _checker = ModelChecker::new(sorted_model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); sorted_model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); assert_eq!(sorted_model.row_count(), 4); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); assert_eq!(sorted_model.row_data(3).unwrap(), 4); // Change the entry with the value 4 to 10 -> maintain order wrapped_rc.set_row_data(1, 10); assert!(observer.added_rows.borrow().is_empty()); assert_eq!(observer.changed_rows.borrow().len(), 1); assert_eq!(*observer.changed_rows.borrow().first().unwrap(), 3); assert!(observer.removed_rows.borrow().is_empty()); assert_eq!(*observer.reset.borrow(), 0); observer.clear(); assert_eq!(sorted_model.row_count(), 4); assert_eq!(sorted_model.row_data(0).unwrap(), 1); assert_eq!(sorted_model.row_data(1).unwrap(), 2); assert_eq!(sorted_model.row_data(2).unwrap(), 3); assert_eq!(sorted_model.row_data(3).unwrap(), 10); // Change the entry with the value 10 to 0 -> new order with remove and insert wrapped_rc.set_row_data(1, 0); assert_eq!(observer.added_rows.borrow().len(), 1); assert!(observer.added_rows.borrow().first().unwrap().eq(&(0, 1))); assert!(observer.changed_rows.borrow().is_empty()); assert_eq!(observer.removed_rows.borrow().len(), 1); assert!(observer.removed_rows.borrow().first().unwrap().eq(&(3, 1))); assert_eq!(*observer.reset.borrow(), 0); observer.clear(); assert_eq!(sorted_model.row_count(), 4); assert_eq!(sorted_model.row_data(0).unwrap(), 0); assert_eq!(sorted_model.row_data(1).unwrap(), 1); assert_eq!(sorted_model.row_data(2).unwrap(), 2); assert_eq!(sorted_model.row_data(3).unwrap(), 3); } #[test] fn test_sorted_model_source_model() { let wrapped_rc = Rc::new(VecModel::from(vec![3, 4, 1, 2])); let model = Rc::new(SortModel::new(wrapped_rc.clone(), |lhs, rhs| lhs.cmp(rhs))); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); model.source_model().push(6); model.source_model().push(5); let expected = &[1, 2, 3, 4, 5, 6]; assert_eq!(model.row_count(), expected.len()); for (i, v) in expected.iter().enumerate() { assert_eq!(model.row_data(i), Some(*v), "Expected {} at index {}", v, i); } } } /// Provides a reversed view of another [`Model`]. /// /// When the other Model is updated, the `ReverseModel` is updated accordingly. /// /// Generic parameters: /// * `M` the type of the wrapped `Model`. /// /// ## Example /// /// Here we have a [`VecModel`] holding [`crate::SharedString`]s. /// It is then reversed into a `ReverseModel`. /// /// ``` /// # use slint::{Model, VecModel, SharedString, ReverseModel}; /// let model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]); /// /// let reverse_model = ReverseModel::new(model); /// /// assert_eq!(reverse_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(reverse_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// assert_eq!(reverse_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// ``` /// /// Alternatively you can use the shortcut [`ModelExt::reverse`]. /// ``` /// # use slint::{Model, ModelExt, VecModel, SharedString}; /// let reverse_model = VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ]).reverse(); /// assert_eq!(reverse_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(reverse_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// assert_eq!(reverse_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// ``` /// /// If you want to modify the underlying [`VecModel`] you can give the ReverseModel a [`Rc`] of it: /// ``` /// # use std::rc::Rc; /// # use slint::{Model, VecModel, SharedString, ReverseModel}; /// let model = Rc::new(VecModel::from(vec![ /// SharedString::from("Lorem"), /// SharedString::from("ipsum"), /// SharedString::from("dolor"), /// ])); /// /// let reverse_model = ReverseModel::new(model.clone()); /// /// assert_eq!(reverse_model.row_data(0).unwrap(), SharedString::from("dolor")); /// assert_eq!(reverse_model.row_data(1).unwrap(), SharedString::from("ipsum")); /// assert_eq!(reverse_model.row_data(2).unwrap(), SharedString::from("Lorem")); /// /// model.push(SharedString::from("opsom")); /// /// assert_eq!(reverse_model.row_data(0).unwrap(), SharedString::from("opsom")); /// assert_eq!(reverse_model.row_data(1).unwrap(), SharedString::from("dolor")); /// assert_eq!(reverse_model.row_data(2).unwrap(), SharedString::from("ipsum")); /// assert_eq!(reverse_model.row_data(3).unwrap(), SharedString::from("Lorem")); /// ``` pub struct ReverseModel(Pin>>>) where M: Model + 'static; struct ReverseModelInner where M: Model + 'static, { wrapped_model: M, notify: ModelNotify, } impl ModelChangeListener for ReverseModelInner where M: Model + 'static, { fn row_changed(self: Pin<&Self>, row: usize) { self.notify.row_changed(self.wrapped_model.row_count() - 1 - row); } fn row_added(self: Pin<&Self>, index: usize, count: usize) { let row_count = self.wrapped_model.row_count(); let old_row_count = row_count - count; let index = old_row_count - index; self.notify.row_added(index, count); } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { let row_count = self.wrapped_model.row_count(); self.notify.row_removed(row_count - index, count); } fn reset(self: Pin<&Self>) { self.notify.reset() } } impl ReverseModel where M: Model + 'static, { /// Creates a new ReverseModel based on the given `wrapped_model`. /// Alternatively you can use [`ModelExt::reverse`] on your Model. pub fn new(wrapped_model: M) -> Self { let inner = ReverseModelInner { wrapped_model, notify: Default::default() }; let container = Box::pin(ModelChangeListenerContainer::new(inner)); container.wrapped_model.model_tracker().attach_peer(container.as_ref().model_peer()); Self(container) } /// Returns a reference to the inner model pub fn source_model(&self) -> &M { &self.0.as_ref().get().get_ref().wrapped_model } } impl Model for ReverseModel where M: Model + 'static, { type Data = M::Data; fn row_count(&self) -> usize { self.0.wrapped_model.row_count() } fn row_data(&self, row: usize) -> Option { let count = self.0.wrapped_model.row_count(); self.0.wrapped_model.row_data(count.checked_sub(row + 1)?) } fn set_row_data(&self, row: usize, data: Self::Data) { let count = self.0.as_ref().wrapped_model.row_count(); self.0.wrapped_model.set_row_data(count - row - 1, data); } fn model_tracker(&self) -> &dyn ModelTracker { &self.0.notify } fn as_any(&self) -> &dyn core::any::Any { self } } #[cfg(test)] mod reversed_tests { use super::*; #[track_caller] fn check_content(model: &ReverseModel>>, expected: &[i32]) { assert_eq!(model.row_count(), expected.len()); for (i, v) in expected.iter().enumerate() { assert_eq!(model.row_data(i), Some(*v), "Expected {} at index {}", v, i); } } #[test] fn test_reversed_model() { let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(ReverseModel::new(wrapped_rc.clone())); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); check_content(&model, &[4, 3, 2, 1]); } #[test] fn test_reversed_model_insert() { for (idx, mapped_idx) in [(0, 4), (1, 3), (2, 2), (3, 1), (4, 0)] { println!("Inserting at {} expecting mapped to {}", idx, mapped_idx); let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(ReverseModel::new(wrapped_rc.clone())); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); wrapped_rc.insert(idx, 10); assert_eq!(observer.added_rows.borrow().len(), 1); assert!( observer.added_rows.borrow().eq(&[(mapped_idx, 1)]), "Added rows: {:?}", observer.added_rows.borrow() ); assert!(observer.changed_rows.borrow().is_empty()); assert!(observer.removed_rows.borrow().is_empty()); assert_eq!(*observer.reset.borrow(), 0); assert_eq!(model.row_data(mapped_idx), Some(10)); } } #[test] fn test_reversed_model_remove() { for (idx, mapped_idx) in [(0, 3), (1, 2), (2, 1), (3, 0)] { println!("Removing at {} expecting mapped to {}", idx, mapped_idx); let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(ReverseModel::new(wrapped_rc.clone())); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); wrapped_rc.remove(idx); assert_eq!(observer.removed_rows.borrow().len(), 1); assert!( observer.removed_rows.borrow().eq(&[(mapped_idx, 1)]), "Remove rows: {:?}", observer.removed_rows.borrow() ); assert!(observer.added_rows.borrow().is_empty()); assert!(observer.changed_rows.borrow().is_empty()); assert_eq!(*observer.reset.borrow(), 0); } } #[test] fn test_reversed_model_changed() { for (idx, mapped_idx) in [(0, 3), (1, 2), (2, 1), (3, 0)] { println!("Changing at {} expecting mapped to {}", idx, mapped_idx); let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(ReverseModel::new(wrapped_rc.clone())); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); wrapped_rc.set_row_data(idx, 10); assert_eq!(observer.changed_rows.borrow().len(), 1); assert!( observer.changed_rows.borrow().eq(&[mapped_idx]), "Changed rows: {:?}", observer.changed_rows.borrow() ); assert!(observer.added_rows.borrow().is_empty()); assert!(observer.removed_rows.borrow().is_empty()); assert_eq!(*observer.reset.borrow(), 0); assert_eq!(model.row_data(mapped_idx), Some(10)); } } #[test] fn test_reversed_model_source_model() { let wrapped_rc = Rc::new(VecModel::from(vec![1, 2, 3, 4])); let model = Rc::new(ReverseModel::new(wrapped_rc.clone())); let _checker = ModelChecker::new(model.clone()); let observer = Box::pin(ModelChangeListenerContainer::::default()); model.model_tracker().attach_peer(Pin::as_ref(&observer).model_peer()); model.source_model().push(5); check_content(&model, &[5, 4, 3, 2, 1]); } } #[test] fn test_long_chain_integrity() { let origin_model = Rc::new(VecModel::from((0..100).collect::>())); let checker1 = ModelChecker::new(origin_model.clone()); let fizzbuzz = Rc::new(MapModel::new(origin_model.clone(), |number| { if (number % 3) == 0 && (number % 5) == 0 { "FizzBuzz".to_owned() } else if (number % 3) == 0 { "Fizz".to_owned() } else if (number % 5) == 0 { "Buzz".to_owned() } else { number.to_string() } })); let checker2 = ModelChecker::new(fizzbuzz.clone()); let filter = Rc::new(FilterModel::new(fizzbuzz, |s| s != "FizzBuzz")); let checker3 = ModelChecker::new(filter.clone()); let reverse = Rc::new(ReverseModel::new(filter)); let checker4 = ModelChecker::new(reverse.clone()); let sorted = Rc::new(SortModel::new_ascending(reverse)); let checker5 = ModelChecker::new(sorted.clone()); let filter2 = Rc::new(FilterModel::new(sorted, |s| s != "Fizz")); let checker6 = ModelChecker::new(filter2.clone()); let check_all = || { checker1.check(); checker2.check(); checker3.check(); checker4.check(); checker5.check(); checker6.check(); }; origin_model.extend(50..150); check_all(); origin_model.insert(8, 1000); check_all(); origin_model.remove(9); check_all(); origin_model.remove(10); origin_model.remove(11); origin_model.set_row_data(55, 10001); check_all(); origin_model.set_row_data(58, 10002); origin_model.set_row_data(59, 10003); origin_model.remove(28); origin_model.remove(29); origin_model.insert(100, 8888); origin_model.remove(30); origin_model.set_row_data(60, 10004); origin_model.remove(130); origin_model.set_row_data(61, 10005); origin_model.remove(131); check_all(); origin_model.remove(12); origin_model.remove(13); origin_model.remove(14); origin_model.set_row_data(62, 10006); origin_model.set_row_data(63, 10007); origin_model.set_row_data(64, 10008); origin_model.set_row_data(65, 10009); check_all(); // Since VecModel don't have this as public API, just add some function that use row_removed on a wider range. impl VecModel { fn remove_range(&self, range: core::ops::Range) { self.array.borrow_mut().drain(range.clone()); self.notify.row_removed(range.start, range.len()) } } origin_model.remove_range(25..110); check_all(); origin_model.extend(900..910); origin_model.set_row_data(45, 44444); origin_model.remove_range(10..30); origin_model.insert(45, 3000); origin_model.insert(45, 3001); origin_model.insert(45, 3002); origin_model.insert(45, 3003); origin_model.insert(45, 3004); origin_model.insert(45, 3006); origin_model.insert(45, 3007); check_all(); }