// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 // cSpell: ignore vecmodel //! Model and Repeater use crate::item_tree::ItemTreeVTable; use crate::item_tree::TraversalOrder; pub use crate::items::{StandardListViewItem, TableColumn}; use crate::layout::Orientation; use crate::lengths::{LogicalLength, RectLengths}; use crate::{Coord, Property, SharedString, SharedVector}; pub use adapters::{FilterModel, MapModel, ReverseModel, SortModel}; use alloc::boxed::Box; use alloc::rc::Rc; use alloc::vec::Vec; use core::cell::{Cell, RefCell}; use core::pin::Pin; use euclid::num::Zero; #[allow(unused)] use euclid::num::{Ceil, Floor}; pub use model_peer::*; use once_cell::unsync::OnceCell; use pin_project::pin_project; mod adapters; mod model_peer; type ItemTreeRc = vtable::VRc; /// This trait defines the interface that users of a model can use to track changes /// to a model. It is supplied via [`Model::model_tracker`] and implementation usually /// return a reference to its field of [`ModelNotify`]. pub trait ModelTracker { /// Attach one peer. The peer will be notified when the model changes fn attach_peer(&self, peer: ModelPeer); /// Register the model as a dependency to the current binding being evaluated, so /// that it will be notified when the model changes its size. fn track_row_count_changes(&self); /// Register a row as a dependency to the current binding being evaluated, so that /// it will be notified when the value of that row changes. fn track_row_data_changes(&self, row: usize); } impl ModelTracker for () { fn attach_peer(&self, _peer: ModelPeer) {} fn track_row_count_changes(&self) {} fn track_row_data_changes(&self, _row: usize) {} } /// A Model is providing Data for the repeated elements with `for` in the `.slint` language /// /// If the model can be changed, the type implementing the Model trait should holds /// a [`ModelNotify`], and is responsible to call functions on it to let the UI know that /// something has changed. /// /// Properties of type array will be mapped to a [`ModelRc`], which wraps a `Rc>.` /// The [`ModelRc`] documentation has examples on how to set models to array properties. /// /// It is more efficient to operate on the model and send changes through the `ModelNotify` rather than /// resetting the property with a different model. /// /// ## Example /// /// As an example, let's see the implementation of [`VecModel`]. /// /// ``` /// # use i_slint_core::model::{Model, ModelNotify, ModelPeer, ModelTracker}; /// pub struct VecModel { /// // the backing data, stored in a `RefCell` as this model can be modified /// array: std::cell::RefCell>, /// // the ModelNotify will allow to notify the UI that the model changes /// notify: ModelNotify, /// } /// /// impl Model for VecModel { /// type Data = T; /// /// fn row_count(&self) -> usize { /// self.array.borrow().len() /// } /// /// fn row_data(&self, row: usize) -> Option { /// self.array.borrow().get(row).cloned() /// } /// /// fn set_row_data(&self, row: usize, data: Self::Data) { /// self.array.borrow_mut()[row] = data; /// // don't forget to call row_changed /// self.notify.row_changed(row); /// } /// /// fn model_tracker(&self) -> &dyn ModelTracker { /// &self.notify /// } /// /// fn as_any(&self) -> &dyn core::any::Any { /// // a typical implementation just return `self` /// self /// } /// } /// /// // when modifying the model, we call the corresponding function in /// // the ModelNotify /// impl VecModel { /// /// Add a row at the end of the model /// pub fn push(&self, value: T) { /// self.array.borrow_mut().push(value); /// self.notify.row_added(self.array.borrow().len() - 1, 1) /// } /// /// /// Remove the row at the given index from the model /// pub fn remove(&self, index: usize) { /// self.array.borrow_mut().remove(index); /// self.notify.row_removed(index, 1) /// } /// } /// ``` pub trait Model { /// The model data: A model is a set of rows and each row has this data type Data; /// The number of rows in the model fn row_count(&self) -> usize; /// Returns the data for a particular row. /// /// This function should normally be called with `row < row_count()` and should return None otherwise. /// /// This function does not register dependencies on the current binding. For an equivalent /// function that tracks dependencies, see [`ModelExt::row_data_tracked`] fn row_data(&self, row: usize) -> Option; /// Sets the data for a particular row. /// /// This function should be called with `row < row_count()`, otherwise the implementation can panic. /// /// If the model cannot support data changes, then it is ok to do nothing. /// The default implementation will print a warning to stderr. /// /// If the model can update the data, it should also call [`ModelNotify::row_changed`] on its /// internal [`ModelNotify`]. fn set_row_data(&self, _row: usize, _data: Self::Data) { #[cfg(feature = "std")] eprintln!( "Model::set_row_data called on a model of type {} which does not re-implement this method. \ This happens when trying to modify a read-only model", core::any::type_name::(), ); } /// The implementation should return a reference to its [`ModelNotify`] field. /// /// You can return `&()` if you your `Model` is constant and does not have a ModelNotify field. fn model_tracker(&self) -> &dyn ModelTracker; /// Returns an iterator visiting all elements of the model. fn iter(&self) -> ModelIterator where Self: Sized, { ModelIterator::new(self) } /// Return something that can be downcast'ed (typically self) /// /// This is useful to get back to the actual model from a [`ModelRc`] stored /// in a ItemTree. /// /// ``` /// # use i_slint_core::model::*; /// # use std::rc::Rc; /// let handle = ModelRc::new(VecModel::from(vec![1i32, 2, 3])); /// // later: /// handle.as_any().downcast_ref::>().unwrap().push(4); /// assert_eq!(handle.row_data(3).unwrap(), 4); /// ``` /// /// Note: the default implementation returns nothing interesting. this method should be /// implemented by model implementation to return something useful. For example: /// ```ignore /// fn as_any(&self) -> &dyn core::any::Any { self } /// ``` fn as_any(&self) -> &dyn core::any::Any { &() } } /// Extension trait with extra methods implemented on types that implement [`Model`] pub trait ModelExt: Model { /// Convenience function that calls [`ModelTracker::track_row_data_changes`] /// before returning [`Model::row_data`]. /// /// Calling [`row_data(row)`](Model::row_data) does not register the row as a dependency when calling it while /// evaluating a property binding. This function calls [`track_row_data_changes(row)`](ModelTracker::track_row_data_changes) /// on the [`self.model_tracker()`](Model::model_tracker) to enable tracking. fn row_data_tracked(&self, row: usize) -> Option { self.model_tracker().track_row_data_changes(row); self.row_data(row) } /// Returns a new Model where all elements are mapped by the function `map_function`. /// This is a shortcut for [`MapModel::new()`]. fn map(self, map_function: F) -> MapModel where Self: Sized + 'static, F: Fn(Self::Data) -> U + 'static, { MapModel::new(self, map_function) } /// Returns a new Model where the elements are filtered by the function `filter_function`. /// This is a shortcut for [`FilterModel::new()`]. fn filter(self, filter_function: F) -> FilterModel where Self: Sized + 'static, F: Fn(&Self::Data) -> bool + 'static, { FilterModel::new(self, filter_function) } /// Returns a new Model where the elements are sorted ascending. /// This is a shortcut for [`SortModel::new_ascending()`]. #[must_use] fn sort(self) -> SortModel where Self: Sized + 'static, Self::Data: core::cmp::Ord, { SortModel::new_ascending(self) } /// Returns a new Model where the elements are sorted by the function `sort_function`. /// This is a shortcut for [`SortModel::new()`]. fn sort_by(self, sort_function: F) -> SortModel where Self: Sized + 'static, F: FnMut(&Self::Data, &Self::Data) -> core::cmp::Ordering + 'static, { SortModel::new(self, sort_function) } /// Returns a new Model where the elements are reversed. /// This is a shortcut for [`ReverseModel::new()`]. fn reverse(self) -> ReverseModel where Self: Sized + 'static, { ReverseModel::new(self) } } impl ModelExt for T {} /// An iterator over the elements of a model. /// This struct is created by the [`Model::iter()`] trait function. pub struct ModelIterator<'a, T> { model: &'a dyn Model, row: usize, } impl<'a, T> ModelIterator<'a, T> { /// Creates a new model iterator for a model reference. /// This is the same as calling [`model.iter()`](Model::iter) pub fn new(model: &'a dyn Model) -> Self { Self { model, row: 0 } } } impl<'a, T> Iterator for ModelIterator<'a, T> { type Item = T; fn next(&mut self) -> Option { let row = self.row; if self.row < self.model.row_count() { self.row += 1; } self.model.row_data(row) } fn size_hint(&self) -> (usize, Option) { let len = self.model.row_count(); (len, Some(len)) } fn nth(&mut self, n: usize) -> Option { self.row = self.row.checked_add(n)?; self.next() } } impl<'a, T> ExactSizeIterator for ModelIterator<'a, T> {} impl Model for Rc { type Data = M::Data; fn row_count(&self) -> usize { (**self).row_count() } fn row_data(&self, row: usize) -> Option { (**self).row_data(row) } fn model_tracker(&self) -> &dyn ModelTracker { (**self).model_tracker() } fn as_any(&self) -> &dyn core::any::Any { (**self).as_any() } fn set_row_data(&self, row: usize, data: Self::Data) { (**self).set_row_data(row, data) } } /// A [`Model`] backed by a `Vec`, using interior mutability. #[derive(Default)] pub struct VecModel { array: RefCell>, notify: ModelNotify, } impl VecModel { /// Allocate a new model from a slice pub fn from_slice(slice: &[T]) -> ModelRc where T: Clone, { ModelRc::new(Self::from(slice.to_vec())) } /// Add a row at the end of the model pub fn push(&self, value: T) { self.array.borrow_mut().push(value); self.notify.row_added(self.array.borrow().len() - 1, 1) } /// Inserts a row at position index. All rows after that are shifted. /// This function panics if index is > row_count(). pub fn insert(&self, index: usize, value: T) { self.array.borrow_mut().insert(index, value); self.notify.row_added(index, 1) } /// Remove the row at the given index from the model /// /// Returns the removed row pub fn remove(&self, index: usize) -> T { let r = self.array.borrow_mut().remove(index); self.notify.row_removed(index, 1); r } /// Replace inner Vec with new data pub fn set_vec(&self, new: impl Into>) { *self.array.borrow_mut() = new.into(); self.notify.reset(); } /// Extend the model with the content of the iterator /// /// Similar to [`Vec::extend`] pub fn extend>(&self, iter: I) { let mut array = self.array.borrow_mut(); let old_idx = array.len(); array.extend(iter); let count = array.len() - old_idx; drop(array); self.notify.row_added(old_idx, count); } /// Clears the model, removing all values /// /// Similar to [`Vec::clear`] pub fn clear(&self) { self.array.borrow_mut().clear(); self.notify.reset(); } /// Swaps two elements in the model. pub fn swap(&self, a: usize, b: usize) { if a == b { return; } self.array.borrow_mut().swap(a, b); self.notify.row_changed(a); self.notify.row_changed(b); } } impl VecModel { /// Appends all the elements in the slice to the model /// /// Similar to [`Vec::extend_from_slice`] pub fn extend_from_slice(&self, src: &[T]) { let mut array = self.array.borrow_mut(); let old_idx = array.len(); array.extend_from_slice(src); drop(array); self.notify.row_added(old_idx, src.len()); } } impl From> for VecModel { fn from(array: Vec) -> Self { VecModel { array: RefCell::new(array), notify: Default::default() } } } impl FromIterator for VecModel { fn from_iter>(iter: I) -> Self { VecModel::from(Vec::from_iter(iter)) } } impl Model for VecModel { type Data = T; fn row_count(&self) -> usize { self.array.borrow().len() } fn row_data(&self, row: usize) -> Option { self.array.borrow().get(row).cloned() } fn set_row_data(&self, row: usize, data: Self::Data) { if row < self.row_count() { self.array.borrow_mut()[row] = data; self.notify.row_changed(row); } } fn model_tracker(&self) -> &dyn ModelTracker { &self.notify } fn as_any(&self) -> &dyn core::any::Any { self } } /// A model backed by a `SharedVector` #[derive(Default)] pub struct SharedVectorModel { array: RefCell>, notify: ModelNotify, } impl SharedVectorModel { /// Add a row at the end of the model pub fn push(&self, value: T) { self.array.borrow_mut().push(value); self.notify.row_added(self.array.borrow().len() - 1, 1) } } impl SharedVectorModel { /// Returns a clone of the model's backing shared vector. pub fn shared_vector(&self) -> SharedVector { self.array.borrow_mut().clone() } } impl From> for SharedVectorModel { fn from(array: SharedVector) -> Self { SharedVectorModel { array: RefCell::new(array), notify: Default::default() } } } impl Model for SharedVectorModel { type Data = T; fn row_count(&self) -> usize { self.array.borrow().len() } fn row_data(&self, row: usize) -> Option { self.array.borrow().get(row).cloned() } fn set_row_data(&self, row: usize, data: Self::Data) { self.array.borrow_mut().make_mut_slice()[row] = data; self.notify.row_changed(row); } fn model_tracker(&self) -> &dyn ModelTracker { &self.notify } fn as_any(&self) -> &dyn core::any::Any { self } } impl Model for usize { type Data = i32; fn row_count(&self) -> usize { *self } fn row_data(&self, row: usize) -> Option { (row < self.row_count()).then_some(row as i32) } fn as_any(&self) -> &dyn core::any::Any { self } fn model_tracker(&self) -> &dyn ModelTracker { &() } } impl Model for bool { type Data = (); fn row_count(&self) -> usize { if *self { 1 } else { 0 } } fn row_data(&self, row: usize) -> Option { (row < self.row_count()).then_some(()) } fn as_any(&self) -> &dyn core::any::Any { self } fn model_tracker(&self) -> &dyn ModelTracker { &() } } /// ModelRc is a type wrapper for a reference counted implementation of the [`Model`] trait. /// /// Models are used to represent sequences of the same data type. In `.slint` code those /// are represented using the `[T]` array syntax and typically used in `for` expressions, /// array properties, and array struct fields. /// /// For example, a `property <[string]> foo` will be of type `ModelRc` /// and, behind the scenes, wraps a `Rc>.` /// /// An array struct field will also be of type `ModelRc`: /// /// ```slint,no-preview /// export struct AddressBook { /// names: [string] /// } /// ``` /// /// When accessing `AddressBook` from Rust, the `names` field will be of type `ModelRc`. /// /// There are several ways of constructing a ModelRc in Rust: /// /// * An empty ModelRc can be constructed with [`ModelRc::default()`]. /// * A `ModelRc` can be constructed from a slice or an array using the [`From`] trait. /// This allocates a [`VecModel`]. /// * Use [`ModelRc::new()`] to construct a `ModelRc` from a type that implements the /// [`Model`] trait, such as [`VecModel`] or your own implementation. /// * If you have your model already in an `Rc`, then you can use the [`From`] trait /// to convert from `Rc>` to `ModelRc`. /// /// ## Example /// /// ```rust /// # i_slint_backend_testing::init_no_event_loop(); /// use slint::{slint, SharedString, ModelRc, Model, VecModel}; /// use std::rc::Rc; /// slint!{ /// import { Button } from "std-widgets.slint"; /// export component Example { /// callback add_item <=> btn.clicked; /// in property <[string]> the_model; /// HorizontalLayout { /// for it in the_model : Text { text: it; } /// btn := Button { text: "Add"; } /// } /// } /// } /// let ui = Example::new().unwrap(); /// // Create a VecModel and put it in an Rc. /// let the_model : Rc> = /// Rc::new(VecModel::from(vec!["Hello".into(), "World".into()])); /// // Convert it to a ModelRc. /// let the_model_rc = ModelRc::from(the_model.clone()); /// // Pass the model to the ui: The generated set_the_model setter from the /// // the_model property takes a ModelRc. /// ui.set_the_model(the_model_rc); /// /// // We have kept a strong reference to the_model, to modify it in a callback. /// ui.on_add_item(move || { /// // Use VecModel API: VecModel uses the Model notification mechanism to let Slint /// // know it needs to refresh the UI. /// the_model.push("SomeValue".into()); /// }); /// /// // Alternative: we can re-use a getter. /// let ui_weak = ui.as_weak(); /// ui.on_add_item(move || { /// let ui = ui_weak.unwrap(); /// let the_model_rc = ui.get_the_model(); /// let the_model = the_model_rc.as_any().downcast_ref::>() /// .expect("We know we set a VecModel earlier"); /// the_model.push("An Item".into()); /// }); /// ``` pub struct ModelRc(Option>>); impl core::fmt::Debug for ModelRc { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "ModelRc(dyn Model)") } } impl Clone for ModelRc { fn clone(&self) -> Self { Self(self.0.clone()) } } impl Default for ModelRc { /// Construct an empty model fn default() -> Self { Self(None) } } impl core::cmp::PartialEq for ModelRc { fn eq(&self, other: &Self) -> bool { match (&self.0, &other.0) { (None, None) => true, (Some(a), Some(b)) => core::ptr::eq( (&**a) as *const dyn Model as *const u8, (&**b) as *const dyn Model as *const u8, ), _ => false, } } } impl ModelRc { pub fn new(model: impl Model + 'static) -> Self { Self(Some(Rc::new(model))) } } impl + 'static> From> for ModelRc { fn from(model: Rc) -> Self { Self(Some(model)) } } impl From + 'static>> for ModelRc { fn from(model: Rc + 'static>) -> Self { Self(Some(model)) } } impl From<&[T]> for ModelRc { fn from(slice: &[T]) -> Self { VecModel::from_slice(slice) } } impl From<[T; N]> for ModelRc { fn from(array: [T; N]) -> Self { VecModel::from_slice(&array) } } impl TryInto>> for ModelRc { type Error = (); fn try_into(self) -> Result>, Self::Error> { self.0.ok_or(()) } } impl Model for ModelRc { type Data = T; fn row_count(&self) -> usize { self.0.as_ref().map_or(0, |model| model.row_count()) } fn row_data(&self, row: usize) -> Option { self.0.as_ref().and_then(|model| model.row_data(row)) } fn set_row_data(&self, row: usize, data: Self::Data) { if let Some(model) = self.0.as_ref() { model.set_row_data(row, data); } } fn model_tracker(&self) -> &dyn ModelTracker { self.0.as_ref().map_or(&(), |model| model.model_tracker()) } fn as_any(&self) -> &dyn core::any::Any { self.0.as_ref().map_or(&(), |model| model.as_any()) } } /// ItemTree that can be instantiated by a repeater. pub trait RepeatedItemTree: crate::item_tree::ItemTree + vtable::HasStaticVTable + 'static { /// The data corresponding to the model type Data: 'static; /// Update this ItemTree at the given index and the given data fn update(&self, index: usize, data: Self::Data); /// Called once after the ItemTree has been instantiated and update() /// was called once. fn init(&self) {} /// Layout this item in the listview /// /// offset_y is the `y` position where this item should be placed. /// it should be updated to be to the y position of the next item. fn listview_layout( self: Pin<&Self>, _offset_y: &mut LogicalLength, _viewport_width: Pin<&Property>, ) { } /// Returns what's needed to perform the layout if this ItemTrees is in a box layout fn box_layout_data( self: Pin<&Self>, _orientation: Orientation, ) -> crate::layout::BoxLayoutCellData { crate::layout::BoxLayoutCellData::default() } } #[derive(Clone, Copy, PartialEq, Debug)] enum RepeatedInstanceState { /// The item is in a clean state Clean, /// The model data is stale and needs to be refreshed Dirty, } struct RepeaterInner { instances: Vec<(RepeatedInstanceState, Option>)>, // The remaining properties only make sense for ListView /// The model row (index) of the first ItemTree in the `instances` vector. offset: usize, /// The average visible item height. cached_item_height: LogicalLength, /// The viewport_y last time the layout of the ListView was done previous_viewport_y: LogicalLength, /// the position of the item in the row `offset` (which corresponds to `instances[0]`). /// We will try to keep this constant when re-layouting items anchor_y: LogicalLength, } impl Default for RepeaterInner { fn default() -> Self { RepeaterInner { instances: Default::default(), offset: 0, cached_item_height: Default::default(), previous_viewport_y: Default::default(), anchor_y: Default::default(), } } } /// This struct is put in a component when using the `for` syntax /// It helps instantiating the ItemTree `T` #[pin_project] pub struct RepeaterTracker { inner: RefCell>, #[pin] model: Property>, #[pin] is_dirty: Property, /// Only used for the list view to track if the scrollbar has changed and item needs to be laid out again. #[pin] listview_geometry_tracker: crate::properties::PropertyTracker, } impl ModelChangeListener for RepeaterTracker { /// Notify the peers that a specific row was changed fn row_changed(self: Pin<&Self>, row: usize) { let mut inner = self.inner.borrow_mut(); let inner = &mut *inner; if let Some(c) = inner.instances.get_mut(row.wrapping_sub(inner.offset)) { if !self.model.is_dirty() { if let Some(comp) = c.1.as_ref() { let model = self.project_ref().model.get_untracked(); if let Some(data) = model.row_data(row) { comp.update(row, data); } c.0 = RepeatedInstanceState::Clean; } } else { c.0 = RepeatedInstanceState::Dirty; } } } /// Notify the peers that rows were added fn row_added(self: Pin<&Self>, mut index: usize, mut count: usize) { let mut inner = self.inner.borrow_mut(); if index < inner.offset { if index + count < inner.offset { return; } count -= inner.offset - index; index = 0; } else { index -= inner.offset; } if count == 0 || index > inner.instances.len() { return; } self.is_dirty.set(true); inner.instances.splice( index..index, core::iter::repeat((RepeatedInstanceState::Dirty, None)).take(count), ); for c in inner.instances[index + count..].iter_mut() { // Because all the indexes are dirty c.0 = RepeatedInstanceState::Dirty; } } /// Notify the peers that rows were removed fn row_removed(self: Pin<&Self>, mut index: usize, mut count: usize) { let mut inner = self.inner.borrow_mut(); if index < inner.offset { if index + count < inner.offset { return; } count -= inner.offset - index; index = 0; } else { index -= inner.offset; } if count == 0 || index >= inner.instances.len() { return; } if (index + count) > inner.instances.len() { count = inner.instances.len() - index; } self.is_dirty.set(true); inner.instances.drain(index..(index + count)); for c in inner.instances[index..].iter_mut() { // Because all the indexes are dirty c.0 = RepeatedInstanceState::Dirty; } } fn reset(self: Pin<&Self>) { self.is_dirty.set(true); self.inner.borrow_mut().instances.clear(); } } impl Default for RepeaterTracker { fn default() -> Self { Self { inner: Default::default(), model: Property::new_named(ModelRc::default(), "i_slint_core::Repeater::model"), is_dirty: Property::new_named(false, "i_slint_core::Repeater::is_dirty"), listview_geometry_tracker: Default::default(), } } } #[pin_project] pub struct Repeater(#[pin] ModelChangeListenerContainer>); impl Default for Repeater { fn default() -> Self { Self(Default::default()) } } impl Repeater { fn data(self: Pin<&Self>) -> Pin<&RepeaterTracker> { self.project_ref().0.get() } fn model(self: Pin<&Self>) -> ModelRc { // Safety: Repeater does not implement drop and never allows access to model as mutable let model = self.data().project_ref().model; if model.is_dirty() { *self.data().inner.borrow_mut() = RepeaterInner::default(); self.data().is_dirty.set(true); let m = model.get(); let peer = self.project_ref().0.model_peer(); m.model_tracker().attach_peer(peer); m } else { model.get() } } /// Call this function to make sure that the model is updated. /// The init function is the function to create a ItemTree pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc) { let model = self.model(); if self.data().project_ref().is_dirty.get() { self.ensure_updated_impl(init, &model, model.row_count()); } } // returns true if new items were created fn ensure_updated_impl( self: Pin<&Self>, init: impl Fn() -> ItemTreeRc, model: &ModelRc, count: usize, ) -> bool { let mut inner = self.0.inner.borrow_mut(); inner.instances.resize_with(count, || (RepeatedInstanceState::Dirty, None)); let offset = inner.offset; let mut any_items_created = false; for (i, c) in inner.instances.iter_mut().enumerate() { if c.0 == RepeatedInstanceState::Dirty { let created = if c.1.is_none() { any_items_created = true; c.1 = Some(init()); true } else { false }; if let Some(data) = model.row_data(i + offset) { c.1.as_ref().unwrap().update(i + offset, data); } if created { c.1.as_ref().unwrap().init(); } c.0 = RepeatedInstanceState::Clean; } } self.data().is_dirty.set(false); any_items_created } /// Same as `Self::ensure_updated` but for a ListView pub fn ensure_updated_listview( self: Pin<&Self>, init: impl Fn() -> ItemTreeRc, viewport_width: Pin<&Property>, viewport_height: Pin<&Property>, viewport_y: Pin<&Property>, listview_width: LogicalLength, listview_height: Pin<&Property>, ) { // Query is_dirty to track model changes self.data().project_ref().is_dirty.get(); self.data().project_ref().is_dirty.set(false); viewport_width.set(listview_width); let model = self.model(); let row_count = model.row_count(); if row_count == 0 { self.0.inner.borrow_mut().instances.clear(); viewport_height.set(LogicalLength::zero()); viewport_y.set(LogicalLength::zero()); return; } let listview_height = listview_height.get(); let mut vp_y = viewport_y.get().min(LogicalLength::zero()); // We need some sort of estimation of the element height let cached_item_height = self.data().inner.borrow_mut().cached_item_height; let element_height = if cached_item_height > LogicalLength::zero() { cached_item_height } else { let total_height = Cell::new(LogicalLength::zero()); let count = Cell::new(0); let get_height_visitor = |x: &ItemTreeRc| { let height = x.as_pin_ref().item_geometry(0).height_length(); count.set(count.get() + 1); total_height.set(total_height.get() + height); }; for c in self.data().inner.borrow().instances.iter() { if let Some(x) = c.1.as_ref() { get_height_visitor(x); } } if count.get() > 0 { total_height.get() / (count.get() as Coord) } else { // There seems to be currently no items. Just instantiate one item. { let mut inner = self.0.inner.borrow_mut(); inner.offset = inner.offset.min(row_count - 1); } self.ensure_updated_impl(&init, &model, 1); if let Some(c) = self.data().inner.borrow().instances.first() { if let Some(x) = c.1.as_ref() { get_height_visitor(x); } } else { panic!("Could not determine size of items"); } total_height.get() } }; let data = self.data(); let mut inner = data.inner.borrow_mut(); if inner.offset >= row_count { inner.offset = row_count - 1; } let one_and_a_half_screen = listview_height * 3 as Coord / 2 as Coord; let first_item_y = inner.anchor_y; let last_item_bottom = first_item_y + element_height * inner.instances.len() as Coord; let (mut new_offset, mut new_offset_y) = if first_item_y > -vp_y + one_and_a_half_screen || last_item_bottom + element_height < -vp_y { // We are jumping more than 1.5 screens, consider this as a random seek. inner.instances.clear(); inner.offset = ((-vp_y / element_height).get().floor() as usize).min(row_count - 1); (inner.offset, -vp_y) } else if vp_y < inner.previous_viewport_y { // we scrolled down, try to find out the new offset. let mut it_y = first_item_y; let mut new_offset = inner.offset; debug_assert!(it_y <= -vp_y); // we scrolled down, the anchor should be hidden for c in inner.instances.iter_mut() { if c.0 == RepeatedInstanceState::Dirty { if c.1.is_none() { c.1 = Some(init()); } if let Some(data) = model.row_data(new_offset) { c.1.as_ref().unwrap().update(new_offset, data); } c.0 = RepeatedInstanceState::Clean; } let h = c.1.as_ref().unwrap().as_pin_ref().item_geometry(0).height_length(); if it_y + h >= -vp_y || new_offset + 1 >= row_count { break; } it_y += h; new_offset += 1; } (new_offset, it_y) } else { // We scrolled up, we'll instantiate items before offset in the loop (inner.offset, first_item_y) }; loop { // If there is a gap before the new_offset and the beginning of the visible viewport, // try to fill it with items. First look at items that are before new_offset in the // inner.instances, if any. while new_offset > inner.offset && new_offset_y > -vp_y { new_offset -= 1; new_offset_y -= inner.instances[new_offset - inner.offset] .1 .as_ref() .unwrap() .as_pin_ref() .item_geometry(0) .height_length(); } // If there is still a gap, fill it with new instances before let mut new_instances = Vec::new(); while new_offset > 0 && new_offset_y > -vp_y { new_offset -= 1; let new_instance = init(); if let Some(data) = model.row_data(new_offset) { new_instance.update(new_offset, data); } new_offset_y -= new_instance.as_pin_ref().item_geometry(0).height_length(); new_instances.push(new_instance); } if !new_instances.is_empty() { inner.instances.splice( 0..0, new_instances .into_iter() .rev() .map(|c| (RepeatedInstanceState::Clean, Some(c))), ); inner.offset = new_offset; } assert!( new_offset >= inner.offset && new_offset <= inner.offset + inner.instances.len() ); // Now we will layout items until we fit the view, starting with the ones that are already instantiated let mut y = new_offset_y; let mut idx = new_offset; let instances_begin = new_offset - inner.offset; for c in &mut inner.instances[instances_begin..] { if idx >= row_count { break; } if c.0 == RepeatedInstanceState::Dirty { if c.1.is_none() { c.1 = Some(init()); } if let Some(data) = model.row_data(idx) { c.1.as_ref().unwrap().update(idx, data); } c.0 = RepeatedInstanceState::Clean; } if let Some(x) = c.1.as_ref() { x.as_pin_ref().listview_layout(&mut y, viewport_width); } idx += 1; if y >= -vp_y + listview_height { break; } } // create more items until there is no more room. while y < -vp_y + listview_height && idx < row_count { let new_instance = init(); if let Some(data) = model.row_data(idx) { new_instance.update(idx, data); } new_instance.as_pin_ref().listview_layout(&mut y, viewport_width); inner.instances.push((RepeatedInstanceState::Clean, Some(new_instance))); idx += 1; } if y < -vp_y + listview_height && vp_y < LogicalLength::zero() { assert!(idx >= row_count); // we reached the end of the model, and we still have room. scroll a bit up. vp_y = listview_height - y; continue; } // Let's cleanup the instances that are not shown. if new_offset != inner.offset { let instances_begin = new_offset - inner.offset; inner.instances.splice(0..instances_begin, core::iter::empty()); inner.offset = new_offset; } if inner.instances.len() != idx - new_offset { inner.instances.splice(idx - new_offset.., core::iter::empty()); } if inner.instances.is_empty() { break; } // Now re-compute some coordinate such a way that the scrollbar are adjusted. inner.cached_item_height = (y - new_offset_y) / inner.instances.len() as Coord; inner.anchor_y = inner.cached_item_height * inner.offset as Coord; viewport_height.set(inner.cached_item_height * row_count as Coord); let new_viewport_y = -inner.anchor_y + vp_y + new_offset_y; viewport_y.set(new_viewport_y); inner.previous_viewport_y = new_viewport_y; break; } } /// Sets the data directly in the model pub fn model_set_row_data(self: Pin<&Self>, row: usize, data: C::Data) { let model = self.model(); model.set_row_data(row, data); } /// Set the model binding pub fn set_model_binding(&self, binding: impl Fn() -> ModelRc + 'static) { self.0.model.set_binding(binding); } /// Call the visitor for the root of each instance pub fn visit( &self, order: TraversalOrder, mut visitor: crate::item_tree::ItemVisitorRefMut, ) -> crate::item_tree::VisitChildrenResult { // We can't keep self.inner borrowed because the event might modify the model let count = self.0.inner.borrow().instances.len() as u32; for i in 0..count { let i = if order == TraversalOrder::BackToFront { i } else { count - i - 1 }; let c = self.0.inner.borrow().instances.get(i as usize).and_then(|c| c.1.clone()); if let Some(c) = c { if c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted() { return crate::item_tree::VisitChildrenResult::abort(i, 0); } } } crate::item_tree::VisitChildrenResult::CONTINUE } /// Return the amount of instances currently in the repeater pub fn len(&self) -> usize { self.0.inner.borrow().instances.len() } /// Return the range of indices used by this Repeater. /// /// Two values are necessary here since the Repeater can start to insert the data from its /// model at an offset. pub fn range(&self) -> core::ops::Range { let inner = self.0.inner.borrow(); core::ops::Range { start: inner.offset, end: inner.offset + inner.instances.len() } } /// Return the instance for the given model index. /// The index should be within [`Self::range()`] pub fn instance_at(&self, index: usize) -> Option> { let inner = self.0.inner.borrow(); inner .instances .get(index - inner.offset) .map(|c| c.1.clone().expect("That was updated before!")) } /// Return true if the Repeater as empty pub fn is_empty(&self) -> bool { self.len() == 0 } /// Returns a vector containing all instances pub fn instances_vec(&self) -> Vec> { self.0.inner.borrow().instances.iter().flat_map(|x| x.1.clone()).collect() } } impl From for StandardListViewItem { fn from(value: SharedString) -> Self { StandardListViewItem { text: value } } } impl From<&str> for StandardListViewItem { fn from(value: &str) -> Self { StandardListViewItem { text: value.into() } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_tracking_model_handle() { let model: Rc> = Rc::new(Default::default()); let handle = ModelRc::from(model.clone() as Rc>); let tracker = Box::pin(crate::properties::PropertyTracker::default()); assert_eq!( tracker.as_ref().evaluate(|| { handle.model_tracker().track_row_count_changes(); handle.row_count() }), 0 ); assert!(!tracker.is_dirty()); model.push(42); model.push(100); assert!(tracker.is_dirty()); assert_eq!( tracker.as_ref().evaluate(|| { handle.model_tracker().track_row_count_changes(); handle.row_count() }), 2 ); assert!(!tracker.is_dirty()); model.set_row_data(0, 41); assert!(!tracker.is_dirty()); model.remove(0); assert!(tracker.is_dirty()); assert_eq!( tracker.as_ref().evaluate(|| { handle.model_tracker().track_row_count_changes(); handle.row_count() }), 1 ); assert!(!tracker.is_dirty()); model.set_vec(vec![1, 2, 3]); assert!(tracker.is_dirty()); } #[test] fn test_data_tracking() { let model: Rc> = Rc::new(VecModel::from(vec![0, 1, 2, 3, 4])); let handle = ModelRc::from(model.clone()); let tracker = Box::pin(crate::properties::PropertyTracker::default()); assert_eq!( tracker.as_ref().evaluate(|| { handle.model_tracker().track_row_data_changes(1); handle.row_data(1).unwrap() }), 1 ); assert!(!tracker.is_dirty()); model.set_row_data(2, 42); assert!(!tracker.is_dirty()); model.set_row_data(1, 100); assert!(tracker.is_dirty()); assert_eq!( tracker.as_ref().evaluate(|| { handle.model_tracker().track_row_data_changes(1); handle.row_data(1).unwrap() }), 100 ); assert!(!tracker.is_dirty()); // Any changes to rows (even if after tracked rows) for now also marks watched rows as dirty, to // keep the logic simple. model.push(200); assert!(tracker.is_dirty()); assert_eq!(tracker.as_ref().evaluate(|| { handle.row_data_tracked(1).unwrap() }), 100); assert!(!tracker.is_dirty()); model.insert(0, 255); assert!(tracker.is_dirty()); model.set_vec(vec![]); assert!(tracker.is_dirty()); } #[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, model: RefCell>>>, } impl TestView { fn clear(&self) { self.changed_rows.borrow_mut().clear(); self.added_rows.borrow_mut().clear(); self.removed_rows.borrow_mut().clear(); *self.reset.borrow_mut() = 0; } fn row_count(&self) -> usize { self.model .borrow() .as_ref() .and_then(|model| model.upgrade()) .map_or(0, |model| model.row_count()) } } impl ModelChangeListener for TestView { fn row_changed(self: Pin<&Self>, row: usize) { self.changed_rows.borrow_mut().push((row, self.row_count())); } fn row_added(self: Pin<&Self>, index: usize, count: usize) { self.added_rows.borrow_mut().push((index, count, self.row_count())); } fn row_removed(self: Pin<&Self>, index: usize, count: usize) { self.removed_rows.borrow_mut().push((index, count, self.row_count())); } fn reset(self: Pin<&Self>) { *self.reset.borrow_mut() += 1; } } #[test] fn test_vecmodel_set_vec() { let view = Box::pin(ModelChangeListenerContainer::::default()); let model = Rc::new(VecModel::from(vec![1i32, 2, 3, 4])); model.model_tracker().attach_peer(Pin::as_ref(&view).model_peer()); *view.model.borrow_mut() = Some(std::rc::Rc::downgrade(&(model.clone() as Rc>))); model.push(5); assert!(view.changed_rows.borrow().is_empty()); assert_eq!(&*view.added_rows.borrow(), &[(4, 1, 5)]); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); model.set_vec(vec![6, 7, 8]); assert!(view.changed_rows.borrow().is_empty()); assert!(view.added_rows.borrow().is_empty()); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 1); view.clear(); model.extend_from_slice(&[9, 10, 11]); assert!(view.changed_rows.borrow().is_empty()); assert_eq!(&*view.added_rows.borrow(), &[(3, 3, 6)]); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); model.extend([12, 13]); assert!(view.changed_rows.borrow().is_empty()); assert_eq!(&*view.added_rows.borrow(), &[(6, 2, 8)]); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); assert_eq!(model.iter().collect::>(), vec![6, 7, 8, 9, 10, 11, 12, 13]); model.swap(1, 1); assert!(view.changed_rows.borrow().is_empty()); assert!(view.added_rows.borrow().is_empty()); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); model.swap(1, 2); assert_eq!(&*view.changed_rows.borrow(), &[(1, 8), (2, 8)]); assert!(view.added_rows.borrow().is_empty()); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); assert_eq!(model.iter().collect::>(), vec![6, 8, 7, 9, 10, 11, 12, 13]); } #[test] fn test_vecmodel_clear() { let view = Box::pin(ModelChangeListenerContainer::::default()); let model = Rc::new(VecModel::from(vec![1, 2, 3, 4])); model.model_tracker().attach_peer(Pin::as_ref(&view).model_peer()); *view.model.borrow_mut() = Some(std::rc::Rc::downgrade(&(model.clone() as Rc>))); model.clear(); assert_eq!(*view.reset.borrow(), 1); assert_eq!(model.row_count(), 0); } #[test] fn test_vecmodel_swap() { let view = Box::pin(ModelChangeListenerContainer::::default()); let model = Rc::new(VecModel::from(vec![1, 2, 3, 4])); model.model_tracker().attach_peer(Pin::as_ref(&view).model_peer()); *view.model.borrow_mut() = Some(std::rc::Rc::downgrade(&(model.clone() as Rc>))); model.swap(1, 1); assert!(view.changed_rows.borrow().is_empty()); assert!(view.added_rows.borrow().is_empty()); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); model.swap(1, 2); assert_eq!(&*view.changed_rows.borrow(), &[(1, 4), (2, 4)]); assert!(view.added_rows.borrow().is_empty()); assert!(view.removed_rows.borrow().is_empty()); assert_eq!(*view.reset.borrow(), 0); view.clear(); } }