1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4// cSpell: ignore vecmodel
5
6//! Model and Repeater
7
8use crate::item_tree::ItemTreeVTable;
9use crate::item_tree::TraversalOrder;
10pub use crate::items::{StandardListViewItem, TableColumn};
11use crate::layout::Orientation;
12use crate::lengths::{LogicalLength, RectLengths};
13use crate::{Coord, Property, SharedString, SharedVector};
14pub use adapters::{FilterModel, MapModel, ReverseModel, SortModel};
15use alloc::boxed::Box;
16use alloc::rc::Rc;
17use alloc::vec::Vec;
18use core::cell::{Cell, RefCell};
19use core::pin::Pin;
20use euclid::num::Zero;
21#[allow(unused)]
22use euclid::num::{Ceil, Floor};
23pub use model_peer::*;
24use once_cell::unsync::OnceCell;
25use pin_project::pin_project;
26
27mod adapters;
28mod model_peer;
29
30type ItemTreeRc<C> = vtable::VRc<crate::item_tree::ItemTreeVTable, C>;
31
32/// This trait defines the interface that users of a model can use to track changes
33/// to a model. It is supplied via [`Model::model_tracker`] and implementation usually
34/// return a reference to its field of [`ModelNotify`].
35pub trait ModelTracker {
36 /// Attach one peer. The peer will be notified when the model changes
37 fn attach_peer(&self, peer: ModelPeer);
38 /// Register the model as a dependency to the current binding being evaluated, so
39 /// that it will be notified when the model changes its size.
40 fn track_row_count_changes(&self);
41 /// Register a row as a dependency to the current binding being evaluated, so that
42 /// it will be notified when the value of that row changes.
43 fn track_row_data_changes(&self, row: usize);
44}
45
46impl ModelTracker for () {
47 fn attach_peer(&self, _peer: ModelPeer) {}
48
49 fn track_row_count_changes(&self) {}
50 fn track_row_data_changes(&self, _row: usize) {}
51}
52
53/// A Model is providing Data for the repeated elements with `for` in the `.slint` language
54///
55/// If the model can be changed, the type implementing the Model trait should holds
56/// a [`ModelNotify`], and is responsible to call functions on it to let the UI know that
57/// something has changed.
58///
59/// Properties of type array will be mapped to a [`ModelRc<T>`], which wraps a `Rc<Model<Data = T>>.`
60/// The [`ModelRc`] documentation has examples on how to set models to array properties.
61///
62/// It is more efficient to operate on the model and send changes through the `ModelNotify` rather than
63/// resetting the property with a different model.
64///
65/// ## Example
66///
67/// As an example, let's see the implementation of [`VecModel`].
68///
69/// ```
70/// # use i_slint_core::model::{Model, ModelNotify, ModelPeer, ModelTracker};
71/// pub struct VecModel<T> {
72/// // the backing data, stored in a `RefCell` as this model can be modified
73/// array: std::cell::RefCell<Vec<T>>,
74/// // the ModelNotify will allow to notify the UI that the model changes
75/// notify: ModelNotify,
76/// }
77///
78/// impl<T: Clone + 'static> Model for VecModel<T> {
79/// type Data = T;
80///
81/// fn row_count(&self) -> usize {
82/// self.array.borrow().len()
83/// }
84///
85/// fn row_data(&self, row: usize) -> Option<Self::Data> {
86/// self.array.borrow().get(row).cloned()
87/// }
88///
89/// fn set_row_data(&self, row: usize, data: Self::Data) {
90/// self.array.borrow_mut()[row] = data;
91/// // don't forget to call row_changed
92/// self.notify.row_changed(row);
93/// }
94///
95/// fn model_tracker(&self) -> &dyn ModelTracker {
96/// &self.notify
97/// }
98///
99/// fn as_any(&self) -> &dyn core::any::Any {
100/// // a typical implementation just return `self`
101/// self
102/// }
103/// }
104///
105/// // when modifying the model, we call the corresponding function in
106/// // the ModelNotify
107/// impl<T> VecModel<T> {
108/// /// Add a row at the end of the model
109/// pub fn push(&self, value: T) {
110/// self.array.borrow_mut().push(value);
111/// self.notify.row_added(self.array.borrow().len() - 1, 1)
112/// }
113///
114/// /// Remove the row at the given index from the model
115/// pub fn remove(&self, index: usize) {
116/// self.array.borrow_mut().remove(index);
117/// self.notify.row_removed(index, 1)
118/// }
119/// }
120/// ```
121pub trait Model {
122 /// The model data: A model is a set of row and each row has this data
123 type Data;
124 /// The amount of row in the model
125 fn row_count(&self) -> usize;
126 /// Returns the data for a particular row. This function should be called with `row < row_count()`.
127 ///
128 /// This function does not register dependencies on the current binding. For an equivalent
129 /// function that tracks dependencies, see [`ModelExt::row_data_tracked`]
130 fn row_data(&self, row: usize) -> Option<Self::Data>;
131 /// Sets the data for a particular row.
132 ///
133 /// This function should be called with `row < row_count()`, otherwise the implementation can panic.
134 ///
135 /// If the model cannot support data changes, then it is ok to do nothing.
136 /// The default implementation will print a warning to stderr.
137 ///
138 /// If the model can update the data, it should also call [`ModelNotify::row_changed`] on its
139 /// internal [`ModelNotify`].
140 fn set_row_data(&self, _row: usize, _data: Self::Data) {
141 #[cfg(feature = "std")]
142 eprintln!(
143 "Model::set_row_data called on a model of type {} which does not re-implement this method. \
144 This happens when trying to modify a read-only model",
145 core::any::type_name::<Self>(),
146 );
147 }
148
149 /// The implementation should return a reference to its [`ModelNotify`] field.
150 ///
151 /// You can return `&()` if you your `Model` is constant and does not have a ModelNotify field.
152 fn model_tracker(&self) -> &dyn ModelTracker;
153
154 /// Returns an iterator visiting all elements of the model.
155 fn iter(&self) -> ModelIterator<Self::Data>
156 where
157 Self: Sized,
158 {
159 ModelIterator::new(self)
160 }
161
162 /// Return something that can be downcast'ed (typically self)
163 ///
164 /// This is useful to get back to the actual model from a [`ModelRc`] stored
165 /// in a ItemTree.
166 ///
167 /// ```
168 /// # use i_slint_core::model::*;
169 /// # use std::rc::Rc;
170 /// let handle = ModelRc::new(VecModel::from(vec![1i32, 2, 3]));
171 /// // later:
172 /// handle.as_any().downcast_ref::<VecModel<i32>>().unwrap().push(4);
173 /// assert_eq!(handle.row_data(3).unwrap(), 4);
174 /// ```
175 ///
176 /// Note: the default implementation returns nothing interesting. this method should be
177 /// implemented by model implementation to return something useful. For example:
178 /// ```ignore
179 /// fn as_any(&self) -> &dyn core::any::Any { self }
180 /// ```
181 fn as_any(&self) -> &dyn core::any::Any {
182 &()
183 }
184}
185
186/// Extension trait with extra methods implemented on types that implement [`Model`]
187pub trait ModelExt: Model {
188 /// Convenience function that calls [`ModelTracker::track_row_data_changes`]
189 /// before returning [`Model::row_data`].
190 ///
191 /// Calling [`row_data(row)`](Model::row_data) does not register the row as a dependency when calling it while
192 /// evaluating a property binding. This function calls [`track_row_data_changes(row)`](ModelTracker::track_row_data_changes)
193 /// on the [`self.model_tracker()`](Model::model_tracker) to enable tracking.
194 fn row_data_tracked(&self, row: usize) -> Option<Self::Data> {
195 self.model_tracker().track_row_data_changes(row);
196 self.row_data(row)
197 }
198
199 /// Returns a new Model where all elements are mapped by the function `map_function`.
200 /// This is a shortcut for [`MapModel::new()`].
201 fn map<F, U>(self, map_function: F) -> MapModel<Self, F>
202 where
203 Self: Sized + 'static,
204 F: Fn(Self::Data) -> U + 'static,
205 {
206 MapModel::new(self, map_function)
207 }
208
209 /// Returns a new Model where the elements are filtered by the function `filter_function`.
210 /// This is a shortcut for [`FilterModel::new()`].
211 fn filter<F>(self, filter_function: F) -> FilterModel<Self, F>
212 where
213 Self: Sized + 'static,
214 F: Fn(&Self::Data) -> bool + 'static,
215 {
216 FilterModel::new(self, filter_function)
217 }
218
219 /// Returns a new Model where the elements are sorted ascending.
220 /// This is a shortcut for [`SortModel::new_ascending()`].
221 #[must_use]
222 fn sort(self) -> SortModel<Self, adapters::AscendingSortHelper>
223 where
224 Self: Sized + 'static,
225 Self::Data: core::cmp::Ord,
226 {
227 SortModel::new_ascending(self)
228 }
229
230 /// Returns a new Model where the elements are sorted by the function `sort_function`.
231 /// This is a shortcut for [`SortModel::new()`].
232 fn sort_by<F>(self, sort_function: F) -> SortModel<Self, F>
233 where
234 Self: Sized + 'static,
235 F: FnMut(&Self::Data, &Self::Data) -> core::cmp::Ordering + 'static,
236 {
237 SortModel::new(self, sort_function)
238 }
239
240 /// Returns a new Model where the elements are reversed.
241 /// This is a shortcut for [`ReverseModel::new()`].
242 fn reverse(self) -> ReverseModel<Self>
243 where
244 Self: Sized + 'static,
245 {
246 ReverseModel::new(self)
247 }
248}
249
250impl<T: Model> ModelExt for T {}
251
252/// An iterator over the elements of a model.
253/// This struct is created by the [`Model::iter()`] trait function.
254pub struct ModelIterator<'a, T> {
255 model: &'a dyn Model<Data = T>,
256 row: usize,
257}
258
259impl<'a, T> ModelIterator<'a, T> {
260 /// Creates a new model iterator for a model reference.
261 /// This is the same as calling [`model.iter()`](Model::iter)
262 pub fn new(model: &'a dyn Model<Data = T>) -> Self {
263 Self { model, row: 0 }
264 }
265}
266
267impl<'a, T> Iterator for ModelIterator<'a, T> {
268 type Item = T;
269
270 fn next(&mut self) -> Option<Self::Item> {
271 let row: usize = self.row;
272 if self.row < self.model.row_count() {
273 self.row += 1;
274 }
275 self.model.row_data(row)
276 }
277
278 fn size_hint(&self) -> (usize, Option<usize>) {
279 let len: usize = self.model.row_count();
280 (len, Some(len))
281 }
282
283 fn nth(&mut self, n: usize) -> Option<Self::Item> {
284 self.row = self.row.checked_add(n)?;
285 self.next()
286 }
287}
288
289impl<'a, T> ExactSizeIterator for ModelIterator<'a, T> {}
290
291impl<M: Model> Model for Rc<M> {
292 type Data = M::Data;
293
294 fn row_count(&self) -> usize {
295 (**self).row_count()
296 }
297
298 fn row_data(&self, row: usize) -> Option<Self::Data> {
299 (**self).row_data(row)
300 }
301
302 fn model_tracker(&self) -> &dyn ModelTracker {
303 (**self).model_tracker()
304 }
305
306 fn as_any(&self) -> &dyn core::any::Any {
307 (**self).as_any()
308 }
309 fn set_row_data(&self, row: usize, data: Self::Data) {
310 (**self).set_row_data(row, data)
311 }
312}
313
314/// A [`Model`] backed by a `Vec<T>`
315#[derive(Default)]
316pub struct VecModel<T> {
317 array: RefCell<Vec<T>>,
318 notify: ModelNotify,
319}
320
321impl<T: 'static> VecModel<T> {
322 /// Allocate a new model from a slice
323 pub fn from_slice(slice: &[T]) -> ModelRc<T>
324 where
325 T: Clone,
326 {
327 ModelRc::new(Self::from(slice.to_vec()))
328 }
329
330 /// Add a row at the end of the model
331 pub fn push(&self, value: T) {
332 self.array.borrow_mut().push(value);
333 self.notify.row_added(self.array.borrow().len() - 1, 1)
334 }
335
336 /// Inserts a row at position index. All rows after that are shifted.
337 /// This function panics if index is > row_count().
338 pub fn insert(&self, index: usize, value: T) {
339 self.array.borrow_mut().insert(index, value);
340 self.notify.row_added(index, 1)
341 }
342
343 /// Remove the row at the given index from the model
344 ///
345 /// Returns the removed row
346 pub fn remove(&self, index: usize) -> T {
347 let r = self.array.borrow_mut().remove(index);
348 self.notify.row_removed(index, 1);
349 r
350 }
351
352 /// Replace inner Vec with new data
353 pub fn set_vec(&self, new: impl Into<Vec<T>>) {
354 *self.array.borrow_mut() = new.into();
355 self.notify.reset();
356 }
357
358 /// Extend the model with the content of the iterator
359 ///
360 /// Similar to [`Vec::extend`]
361 pub fn extend<I: IntoIterator<Item = T>>(&self, iter: I) {
362 let mut array = self.array.borrow_mut();
363 let old_idx = array.len();
364 array.extend(iter);
365 let count = array.len() - old_idx;
366 drop(array);
367 self.notify.row_added(old_idx, count);
368 }
369}
370
371impl<T: Clone + 'static> VecModel<T> {
372 /// Appends all the elements in the slice to the model
373 ///
374 /// Similar to [`Vec::extend_from_slice`]
375 pub fn extend_from_slice(&self, src: &[T]) {
376 let mut array: RefMut<'_, Vec> = self.array.borrow_mut();
377 let old_idx: usize = array.len();
378
379 array.extend_from_slice(src);
380 drop(array);
381 self.notify.row_added(index:old_idx, count:src.len());
382 }
383}
384
385impl<T> From<Vec<T>> for VecModel<T> {
386 fn from(array: Vec<T>) -> Self {
387 VecModel { array: RefCell::new(array), notify: Default::default() }
388 }
389}
390
391impl<T: Clone + 'static> Model for VecModel<T> {
392 type Data = T;
393
394 fn row_count(&self) -> usize {
395 self.array.borrow().len()
396 }
397
398 fn row_data(&self, row: usize) -> Option<Self::Data> {
399 self.array.borrow().get(row).cloned()
400 }
401
402 fn set_row_data(&self, row: usize, data: Self::Data) {
403 if row < self.row_count() {
404 self.array.borrow_mut()[row] = data;
405 self.notify.row_changed(row);
406 }
407 }
408
409 fn model_tracker(&self) -> &dyn ModelTracker {
410 &self.notify
411 }
412
413 fn as_any(&self) -> &dyn core::any::Any {
414 self
415 }
416}
417
418/// A model backed by a `SharedVector<T>`
419#[derive(Default)]
420pub struct SharedVectorModel<T> {
421 array: RefCell<SharedVector<T>>,
422 notify: ModelNotify,
423}
424
425impl<T: Clone + 'static> SharedVectorModel<T> {
426 /// Add a row at the end of the model
427 pub fn push(&self, value: T) {
428 self.array.borrow_mut().push(value);
429 self.notify.row_added(self.array.borrow().len() - 1, count:1)
430 }
431}
432
433impl<T> SharedVectorModel<T> {
434 /// Returns a clone of the model's backing shared vector.
435 pub fn shared_vector(&self) -> SharedVector<T> {
436 self.array.borrow_mut().clone()
437 }
438}
439
440impl<T> From<SharedVector<T>> for SharedVectorModel<T> {
441 fn from(array: SharedVector<T>) -> Self {
442 SharedVectorModel { array: RefCell::new(array), notify: Default::default() }
443 }
444}
445
446impl<T: Clone + 'static> Model for SharedVectorModel<T> {
447 type Data = T;
448
449 fn row_count(&self) -> usize {
450 self.array.borrow().len()
451 }
452
453 fn row_data(&self, row: usize) -> Option<Self::Data> {
454 self.array.borrow().get(index:row).cloned()
455 }
456
457 fn set_row_data(&self, row: usize, data: Self::Data) {
458 self.array.borrow_mut().make_mut_slice()[row] = data;
459 self.notify.row_changed(row);
460 }
461
462 fn model_tracker(&self) -> &dyn ModelTracker {
463 &self.notify
464 }
465
466 fn as_any(&self) -> &dyn core::any::Any {
467 self
468 }
469}
470
471impl Model for usize {
472 type Data = i32;
473
474 fn row_count(&self) -> usize {
475 *self
476 }
477
478 fn row_data(&self, row: usize) -> Option<Self::Data> {
479 (row < self.row_count()).then_some(row as i32)
480 }
481
482 fn as_any(&self) -> &dyn core::any::Any {
483 self
484 }
485
486 fn model_tracker(&self) -> &dyn ModelTracker {
487 &()
488 }
489}
490
491impl Model for bool {
492 type Data = ();
493
494 fn row_count(&self) -> usize {
495 if *self {
496 1
497 } else {
498 0
499 }
500 }
501
502 fn row_data(&self, row: usize) -> Option<Self::Data> {
503 (row < self.row_count()).then_some(())
504 }
505
506 fn as_any(&self) -> &dyn core::any::Any {
507 self
508 }
509
510 fn model_tracker(&self) -> &dyn ModelTracker {
511 &()
512 }
513}
514
515/// ModelRc is a type wrapper for a reference counted implementation of the [`Model`] trait.
516///
517/// Models are used to represent sequences of the same data type. In `.slint` code those
518/// are represented using the `[T]` array syntax and typically used in `for` expressions,
519/// array properties, and array struct fields.
520///
521/// For example, a `property <[string]> foo` will be of type `ModelRc<SharedString>`
522/// and, behind the scenes, wraps a `Rc<dyn Model<Data = SharedString>>.`
523///
524/// An array struct field will also be of type `ModelRc`:
525///
526/// ```slint,no-preview
527/// export struct AddressBook {
528/// names: [string]
529/// }
530/// ```
531///
532/// When accessing `AddressBook` from Rust, the `names` field will be of type `ModelRc<SharedString>`.
533///
534/// There are several ways of constructing a ModelRc in Rust:
535///
536/// * An empty ModelRc can be constructed with [`ModelRc::default()`].
537/// * A `ModelRc` can be constructed from a slice or an array using the [`From`] trait.
538/// This allocates a [`VecModel`].
539/// * Use [`ModelRc::new()`] to construct a `ModelRc` from a type that implements the
540/// [`Model`] trait, such as [`VecModel`] or your own implementation.
541/// * If you have your model already in an `Rc`, then you can use the [`From`] trait
542/// to convert from `Rc<dyn Model<Data = T>>` to `ModelRc`.
543///
544/// ## Example
545///
546/// ```rust
547/// # i_slint_backend_testing::init();
548/// use slint::{slint, SharedString, ModelRc, Model, VecModel};
549/// use std::rc::Rc;
550/// slint!{
551/// import { Button } from "std-widgets.slint";
552/// export component Example {
553/// callback add_item <=> btn.clicked;
554/// in property <[string]> the_model;
555/// HorizontalLayout {
556/// for it in the_model : Text { text: it; }
557/// btn := Button { text: "Add"; }
558/// }
559/// }
560/// }
561/// let ui = Example::new().unwrap();
562/// // Create a VecModel and put it in an Rc.
563/// let the_model : Rc<VecModel<SharedString>> =
564/// Rc::new(VecModel::from(vec!["Hello".into(), "World".into()]));
565/// // Convert it to a ModelRc.
566/// let the_model_rc = ModelRc::from(the_model.clone());
567/// // Pass the model to the ui: The generated set_the_model setter from the
568/// // the_model property takes a ModelRc.
569/// ui.set_the_model(the_model_rc);
570///
571/// // We have kept a strong reference to the_model, to modify it in a callback.
572/// ui.on_add_item(move || {
573/// // Use VecModel API: VecModel uses the Model notification mechanism to let Slint
574/// // know it needs to refresh the UI.
575/// the_model.push("SomeValue".into());
576/// });
577///
578/// // Alternative: we can re-use a getter.
579/// let ui_weak = ui.as_weak();
580/// ui.on_add_item(move || {
581/// let ui = ui_weak.unwrap();
582/// let the_model_rc = ui.get_the_model();
583/// let the_model = the_model_rc.as_any().downcast_ref::<VecModel<SharedString>>()
584/// .expect("We know we set a VecModel earlier");
585/// the_model.push("An Item".into());
586/// });
587/// ```
588pub struct ModelRc<T>(Option<Rc<dyn Model<Data = T>>>);
589
590impl<T> core::fmt::Debug for ModelRc<T> {
591 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
592 write!(f, "ModelRc(dyn Model)")
593 }
594}
595
596impl<T> Clone for ModelRc<T> {
597 fn clone(&self) -> Self {
598 Self(self.0.clone())
599 }
600}
601
602impl<T> Default for ModelRc<T> {
603 /// Construct an empty model
604 fn default() -> Self {
605 Self(None)
606 }
607}
608
609impl<T> core::cmp::PartialEq for ModelRc<T> {
610 fn eq(&self, other: &Self) -> bool {
611 match (&self.0, &other.0) {
612 (None, None) => true,
613 (Some(a: &Rc>), Some(b: &Rc>)) => core::ptr::eq(
614 (&**a) as *const dyn Model<Data = T> as *const u8,
615 (&**b) as *const dyn Model<Data = T> as *const u8,
616 ),
617 _ => false,
618 }
619 }
620}
621
622impl<T> ModelRc<T> {
623 pub fn new(model: impl Model<Data = T> + 'static) -> Self {
624 Self(Some(Rc::new(model)))
625 }
626}
627
628impl<T, M: Model<Data = T> + 'static> From<Rc<M>> for ModelRc<T> {
629 fn from(model: Rc<M>) -> Self {
630 Self(Some(model))
631 }
632}
633
634impl<T> From<Rc<dyn Model<Data = T> + 'static>> for ModelRc<T> {
635 fn from(model: Rc<dyn Model<Data = T> + 'static>) -> Self {
636 Self(Some(model))
637 }
638}
639
640impl<T: Clone + 'static> From<&[T]> for ModelRc<T> {
641 fn from(slice: &[T]) -> Self {
642 VecModel::from_slice(slice)
643 }
644}
645
646impl<T: Clone + 'static, const N: usize> From<[T; N]> for ModelRc<T> {
647 fn from(array: [T; N]) -> Self {
648 VecModel::from_slice(&array)
649 }
650}
651
652impl<T> TryInto<Rc<dyn Model<Data = T>>> for ModelRc<T> {
653 type Error = ();
654
655 fn try_into(self) -> Result<Rc<dyn Model<Data = T>>, Self::Error> {
656 self.0.ok_or(())
657 }
658}
659
660impl<T> Model for ModelRc<T> {
661 type Data = T;
662
663 fn row_count(&self) -> usize {
664 self.0.as_ref().map_or(0, |model| model.row_count())
665 }
666
667 fn row_data(&self, row: usize) -> Option<Self::Data> {
668 self.0.as_ref().and_then(|model| model.row_data(row))
669 }
670
671 fn set_row_data(&self, row: usize, data: Self::Data) {
672 if let Some(model) = self.0.as_ref() {
673 model.set_row_data(row, data);
674 }
675 }
676
677 fn model_tracker(&self) -> &dyn ModelTracker {
678 self.0.as_ref().map_or(&(), |model| model.model_tracker())
679 }
680
681 fn as_any(&self) -> &dyn core::any::Any {
682 self.0.as_ref().map_or(&(), |model| model.as_any())
683 }
684}
685
686/// ItemTree that can be instantiated by a repeater.
687pub trait RepeatedItemTree:
688 crate::item_tree::ItemTree + vtable::HasStaticVTable<ItemTreeVTable> + 'static
689{
690 /// The data corresponding to the model
691 type Data: 'static;
692
693 /// Update this ItemTree at the given index and the given data
694 fn update(&self, index: usize, data: Self::Data);
695
696 /// Called once after the ItemTree has been instantiated and update()
697 /// was called once.
698 fn init(&self) {}
699
700 /// Layout this item in the listview
701 ///
702 /// offset_y is the `y` position where this item should be placed.
703 /// it should be updated to be to the y position of the next item.
704 fn listview_layout(
705 self: Pin<&Self>,
706 _offset_y: &mut LogicalLength,
707 _viewport_width: Pin<&Property<LogicalLength>>,
708 ) {
709 }
710
711 /// Returns what's needed to perform the layout if this ItemTrees is in a box layout
712 fn box_layout_data(
713 self: Pin<&Self>,
714 _orientation: Orientation,
715 ) -> crate::layout::BoxLayoutCellData {
716 crate::layout::BoxLayoutCellData::default()
717 }
718}
719
720#[derive(Clone, Copy, PartialEq, Debug)]
721enum RepeatedInstanceState {
722 /// The item is in a clean state
723 Clean,
724 /// The model data is stale and needs to be refreshed
725 Dirty,
726}
727struct RepeaterInner<C: RepeatedItemTree> {
728 instances: Vec<(RepeatedInstanceState, Option<ItemTreeRc<C>>)>,
729
730 // The remaining properties only make sense for ListView
731 /// The model row (index) of the first ItemTree in the `instances` vector.
732 offset: usize,
733 /// The average visible item height.
734 cached_item_height: LogicalLength,
735 /// The viewport_y last time the layout of the ListView was done
736 previous_viewport_y: LogicalLength,
737 /// the position of the item in the row `offset` (which corresponds to `instances[0]`).
738 /// We will try to keep this constant when re-layouting items
739 anchor_y: LogicalLength,
740}
741
742impl<C: RepeatedItemTree> Default for RepeaterInner<C> {
743 fn default() -> Self {
744 RepeaterInner {
745 instances: Default::default(),
746 offset: 0,
747 cached_item_height: Default::default(),
748 previous_viewport_y: Default::default(),
749 anchor_y: Default::default(),
750 }
751 }
752}
753
754/// This struct is put in a component when using the `for` syntax
755/// It helps instantiating the ItemTree `T`
756#[pin_project]
757pub struct RepeaterTracker<T: RepeatedItemTree> {
758 inner: RefCell<RepeaterInner<T>>,
759 #[pin]
760 model: Property<ModelRc<T::Data>>,
761 #[pin]
762 is_dirty: Property<bool>,
763 /// Only used for the list view to track if the scrollbar has changed and item needs to be laid out again.
764 #[pin]
765 listview_geometry_tracker: crate::properties::PropertyTracker,
766}
767
768impl<T: RepeatedItemTree> ModelChangeListener for RepeaterTracker<T> {
769 /// Notify the peers that a specific row was changed
770 fn row_changed(self: Pin<&Self>, row: usize) {
771 let mut inner = self.inner.borrow_mut();
772 let inner = &mut *inner;
773 if let Some(c) = inner.instances.get_mut(row.wrapping_sub(inner.offset)) {
774 if !self.model.is_dirty() {
775 if let Some(comp) = c.1.as_ref() {
776 let model = self.project_ref().model.get_untracked();
777 comp.update(row, model.row_data(row).unwrap());
778 c.0 = RepeatedInstanceState::Clean;
779 }
780 } else {
781 c.0 = RepeatedInstanceState::Dirty;
782 }
783 }
784 }
785 /// Notify the peers that rows were added
786 fn row_added(self: Pin<&Self>, mut index: usize, mut count: usize) {
787 let mut inner = self.inner.borrow_mut();
788 if index < inner.offset {
789 if index + count < inner.offset {
790 return;
791 }
792 count -= inner.offset - index;
793 index = 0;
794 } else {
795 index -= inner.offset;
796 }
797 if count == 0 || index > inner.instances.len() {
798 return;
799 }
800 self.is_dirty.set(true);
801 inner.instances.splice(
802 index..index,
803 core::iter::repeat((RepeatedInstanceState::Dirty, None)).take(count),
804 );
805 for c in inner.instances[index + count..].iter_mut() {
806 // Because all the indexes are dirty
807 c.0 = RepeatedInstanceState::Dirty;
808 }
809 }
810 /// Notify the peers that rows were removed
811 fn row_removed(self: Pin<&Self>, mut index: usize, mut count: usize) {
812 let mut inner = self.inner.borrow_mut();
813 if index < inner.offset {
814 if index + count < inner.offset {
815 return;
816 }
817 count -= inner.offset - index;
818 index = 0;
819 } else {
820 index -= inner.offset;
821 }
822 if count == 0 || index >= inner.instances.len() {
823 return;
824 }
825 if (index + count) > inner.instances.len() {
826 count = inner.instances.len() - index;
827 }
828 self.is_dirty.set(true);
829 inner.instances.drain(index..(index + count));
830 for c in inner.instances[index..].iter_mut() {
831 // Because all the indexes are dirty
832 c.0 = RepeatedInstanceState::Dirty;
833 }
834 }
835
836 fn reset(self: Pin<&Self>) {
837 self.is_dirty.set(true);
838 self.inner.borrow_mut().instances.clear();
839 }
840}
841
842impl<C: RepeatedItemTree> Default for RepeaterTracker<C> {
843 fn default() -> Self {
844 Self {
845 inner: Default::default(),
846 model: Property::new_named(value:ModelRc::default(), _name:"i_slint_core::Repeater::model"),
847 is_dirty: Property::new_named(value:false, _name:"i_slint_core::Repeater::is_dirty"),
848 listview_geometry_tracker: Default::default(),
849 }
850 }
851}
852
853#[pin_project]
854pub struct Repeater<C: RepeatedItemTree>(#[pin] ModelChangeListenerContainer<RepeaterTracker<C>>);
855
856impl<C: RepeatedItemTree> Default for Repeater<C> {
857 fn default() -> Self {
858 Self(Default::default())
859 }
860}
861
862impl<C: RepeatedItemTree + 'static> Repeater<C> {
863 fn data(self: Pin<&Self>) -> Pin<&RepeaterTracker<C>> {
864 self.project_ref().0.get()
865 }
866
867 fn model(self: Pin<&Self>) -> ModelRc<C::Data> {
868 // Safety: Repeater does not implement drop and never allows access to model as mutable
869 let model = self.data().project_ref().model;
870
871 if model.is_dirty() {
872 *self.data().inner.borrow_mut() = RepeaterInner::default();
873 self.data().is_dirty.set(true);
874 let m = model.get();
875 let peer = self.project_ref().0.model_peer();
876 m.model_tracker().attach_peer(peer);
877 m
878 } else {
879 model.get()
880 }
881 }
882
883 /// Call this function to make sure that the model is updated.
884 /// The init function is the function to create a ItemTree
885 pub fn ensure_updated(self: Pin<&Self>, init: impl Fn() -> ItemTreeRc<C>) {
886 let model = self.model();
887 if self.data().project_ref().is_dirty.get() {
888 self.ensure_updated_impl(init, &model, model.row_count());
889 }
890 }
891
892 // returns true if new items were created
893 fn ensure_updated_impl(
894 self: Pin<&Self>,
895 init: impl Fn() -> ItemTreeRc<C>,
896 model: &ModelRc<C::Data>,
897 count: usize,
898 ) -> bool {
899 let mut inner = self.0.inner.borrow_mut();
900 inner.instances.resize_with(count, || (RepeatedInstanceState::Dirty, None));
901 let offset = inner.offset;
902 let mut any_items_created = false;
903 for (i, c) in inner.instances.iter_mut().enumerate() {
904 if c.0 == RepeatedInstanceState::Dirty {
905 let created = if c.1.is_none() {
906 any_items_created = true;
907 c.1 = Some(init());
908 true
909 } else {
910 false
911 };
912 c.1.as_ref().unwrap().update(i + offset, model.row_data(i + offset).unwrap());
913 if created {
914 c.1.as_ref().unwrap().init();
915 }
916 c.0 = RepeatedInstanceState::Clean;
917 }
918 }
919 self.data().is_dirty.set(false);
920 any_items_created
921 }
922
923 /// Same as `Self::ensure_updated` but for a ListView
924 pub fn ensure_updated_listview(
925 self: Pin<&Self>,
926 init: impl Fn() -> ItemTreeRc<C>,
927 viewport_width: Pin<&Property<LogicalLength>>,
928 viewport_height: Pin<&Property<LogicalLength>>,
929 viewport_y: Pin<&Property<LogicalLength>>,
930 listview_width: LogicalLength,
931 listview_height: Pin<&Property<LogicalLength>>,
932 ) {
933 // Query is_dirty to track model changes
934 self.data().project_ref().is_dirty.get();
935 self.data().project_ref().is_dirty.set(false);
936
937 viewport_width.set(listview_width);
938 let model = self.model();
939 let row_count = model.row_count();
940 if row_count == 0 {
941 self.0.inner.borrow_mut().instances.clear();
942 viewport_height.set(LogicalLength::zero());
943 viewport_y.set(LogicalLength::zero());
944
945 return;
946 }
947
948 let listview_height = listview_height.get();
949 let mut vp_y = viewport_y.get().min(LogicalLength::zero());
950
951 // We need some sort of estimation of the element height
952 let cached_item_height = self.data().inner.borrow_mut().cached_item_height;
953 let element_height = if cached_item_height > LogicalLength::zero() {
954 cached_item_height
955 } else {
956 let total_height = Cell::new(LogicalLength::zero());
957 let count = Cell::new(0);
958 let get_height_visitor = |x: &ItemTreeRc<C>| {
959 let height = x.as_pin_ref().item_geometry(0).height_length();
960 count.set(count.get() + 1);
961 total_height.set(total_height.get() + height);
962 };
963 for c in self.data().inner.borrow().instances.iter() {
964 if let Some(x) = c.1.as_ref() {
965 get_height_visitor(x);
966 }
967 }
968
969 if count.get() > 0 {
970 total_height.get() / (count.get() as Coord)
971 } else {
972 // There seems to be currently no items. Just instantiate one item.
973 {
974 let mut inner = self.0.inner.borrow_mut();
975 inner.offset = inner.offset.min(row_count - 1);
976 }
977
978 self.ensure_updated_impl(&init, &model, 1);
979 if let Some(c) = self.data().inner.borrow().instances.first() {
980 if let Some(x) = c.1.as_ref() {
981 get_height_visitor(x);
982 }
983 } else {
984 panic!("Could not determine size of items");
985 }
986 total_height.get()
987 }
988 };
989
990 let data = self.data();
991 let mut inner = data.inner.borrow_mut();
992 if inner.offset >= row_count {
993 inner.offset = row_count - 1;
994 }
995
996 let one_and_a_half_screen = listview_height * 3 as Coord / 2 as Coord;
997 let first_item_y = inner.anchor_y;
998 let last_item_bottom = first_item_y + element_height * inner.instances.len() as Coord;
999
1000 let (mut new_offset, mut new_offset_y) = if first_item_y > -vp_y + one_and_a_half_screen
1001 || last_item_bottom + element_height < -vp_y
1002 {
1003 // We are jumping more than 1.5 screens, consider this as a random seek.
1004 inner.instances.clear();
1005 inner.offset = ((-vp_y / element_height).get().floor() as usize).min(row_count - 1);
1006 (inner.offset, -vp_y)
1007 } else if vp_y < inner.previous_viewport_y {
1008 // we scrolled down, try to find out the new offset.
1009 let mut it_y = first_item_y;
1010 let mut new_offset = inner.offset;
1011 debug_assert!(it_y <= -vp_y); // we scrolled down, the anchor should be hidden
1012 for c in inner.instances.iter_mut() {
1013 if c.0 == RepeatedInstanceState::Dirty {
1014 if c.1.is_none() {
1015 c.1 = Some(init());
1016 }
1017 c.1.as_ref().unwrap().update(new_offset, model.row_data(new_offset).unwrap());
1018 c.0 = RepeatedInstanceState::Clean;
1019 }
1020 let h = c.1.as_ref().unwrap().as_pin_ref().item_geometry(0).height_length();
1021 if it_y + h >= -vp_y || new_offset + 1 >= row_count {
1022 break;
1023 }
1024 it_y += h;
1025 new_offset += 1;
1026 }
1027 (new_offset, it_y)
1028 } else {
1029 // We scrolled up, we'll instantiate items before offset in the loop
1030 (inner.offset, first_item_y)
1031 };
1032
1033 loop {
1034 // If there is a gap before the new_offset and the beginning of the visible viewport,
1035 // try to fill it with items. First look at items that are before new_offset in the
1036 // inner.instances, if any.
1037 while new_offset > inner.offset && new_offset_y > -vp_y {
1038 new_offset -= 1;
1039 new_offset_y -= inner.instances[new_offset - inner.offset]
1040 .1
1041 .as_ref()
1042 .unwrap()
1043 .as_pin_ref()
1044 .item_geometry(0)
1045 .height_length();
1046 }
1047 // If there is still a gap, fill it with new instances before
1048 let mut new_instances = Vec::new();
1049 while new_offset > 0 && new_offset_y > -vp_y {
1050 new_offset -= 1;
1051 let new_instance = init();
1052 new_instance.update(new_offset, model.row_data(new_offset).unwrap());
1053 new_offset_y -= new_instance.as_pin_ref().item_geometry(0).height_length();
1054 new_instances.push(new_instance);
1055 }
1056 if !new_instances.is_empty() {
1057 inner.instances.splice(
1058 0..0,
1059 new_instances
1060 .into_iter()
1061 .rev()
1062 .map(|c| (RepeatedInstanceState::Clean, Some(c))),
1063 );
1064 inner.offset = new_offset;
1065 }
1066 assert!(
1067 new_offset >= inner.offset && new_offset <= inner.offset + inner.instances.len()
1068 );
1069
1070 // Now we will layout items until we fit the view, starting with the ones that are already instantiated
1071 let mut y = new_offset_y;
1072 let mut idx = new_offset;
1073 let instances_begin = new_offset - inner.offset;
1074 for c in &mut inner.instances[instances_begin..] {
1075 if idx >= row_count {
1076 break;
1077 }
1078 if c.0 == RepeatedInstanceState::Dirty {
1079 if c.1.is_none() {
1080 c.1 = Some(init());
1081 }
1082 c.1.as_ref().unwrap().update(idx, model.row_data(idx).unwrap());
1083 c.0 = RepeatedInstanceState::Clean;
1084 }
1085 if let Some(x) = c.1.as_ref() {
1086 x.as_pin_ref().listview_layout(&mut y, viewport_width);
1087 }
1088 idx += 1;
1089 if y >= -vp_y + listview_height {
1090 break;
1091 }
1092 }
1093
1094 // create more items until there is no more room.
1095 while y < -vp_y + listview_height && idx < row_count {
1096 let new_instance = init();
1097 new_instance.update(idx, model.row_data(idx).unwrap());
1098 new_instance.as_pin_ref().listview_layout(&mut y, viewport_width);
1099 inner.instances.push((RepeatedInstanceState::Clean, Some(new_instance)));
1100 idx += 1;
1101 }
1102 if y < -vp_y + listview_height && vp_y < LogicalLength::zero() {
1103 assert!(idx >= row_count);
1104 // we reached the end of the model, and we still have room. scroll a bit up.
1105 vp_y = listview_height - y;
1106 continue;
1107 }
1108
1109 // Let's cleanup the instances that are not shown.
1110 if new_offset != inner.offset {
1111 let instances_begin = new_offset - inner.offset;
1112 inner.instances.splice(0..instances_begin, core::iter::empty());
1113 inner.offset = new_offset;
1114 }
1115 if inner.instances.len() != idx - new_offset {
1116 inner.instances.splice(idx - new_offset.., core::iter::empty());
1117 }
1118
1119 if inner.instances.is_empty() {
1120 break;
1121 }
1122
1123 // Now re-compute some coordinate such a way that the scrollbar are adjusted.
1124 inner.cached_item_height = (y - new_offset_y) / inner.instances.len() as Coord;
1125 inner.anchor_y = inner.cached_item_height * inner.offset as Coord;
1126 viewport_height.set(inner.cached_item_height * row_count as Coord);
1127 let new_viewport_y = -inner.anchor_y + vp_y + new_offset_y;
1128 viewport_y.set(new_viewport_y);
1129 inner.previous_viewport_y = new_viewport_y;
1130 break;
1131 }
1132 }
1133
1134 /// Sets the data directly in the model
1135 pub fn model_set_row_data(self: Pin<&Self>, row: usize, data: C::Data) {
1136 let model = self.model();
1137 model.set_row_data(row, data);
1138 }
1139
1140 /// Set the model binding
1141 pub fn set_model_binding(&self, binding: impl Fn() -> ModelRc<C::Data> + 'static) {
1142 self.0.model.set_binding(binding);
1143 }
1144
1145 /// Call the visitor for the root of each instance
1146 pub fn visit(
1147 &self,
1148 order: TraversalOrder,
1149 mut visitor: crate::item_tree::ItemVisitorRefMut,
1150 ) -> crate::item_tree::VisitChildrenResult {
1151 // We can't keep self.inner borrowed because the event might modify the model
1152 let count = self.0.inner.borrow().instances.len() as u32;
1153 for i in 0..count {
1154 let i = if order == TraversalOrder::BackToFront { i } else { count - i - 1 };
1155 let c = self.0.inner.borrow().instances.get(i as usize).and_then(|c| c.1.clone());
1156 if let Some(c) = c {
1157 if c.as_pin_ref().visit_children_item(-1, order, visitor.borrow_mut()).has_aborted()
1158 {
1159 return crate::item_tree::VisitChildrenResult::abort(i, 0);
1160 }
1161 }
1162 }
1163 crate::item_tree::VisitChildrenResult::CONTINUE
1164 }
1165
1166 /// Return the amount of instances currently in the repeater
1167 pub fn len(&self) -> usize {
1168 self.0.inner.borrow().instances.len()
1169 }
1170
1171 /// Return the range of indices used by this Repeater.
1172 ///
1173 /// Two values are necessary here since the Repeater can start to insert the data from its
1174 /// model at an offset.
1175 pub fn range(&self) -> core::ops::Range<usize> {
1176 let inner = self.0.inner.borrow();
1177 core::ops::Range { start: inner.offset, end: inner.offset + inner.instances.len() }
1178 }
1179
1180 /// Return the instance for the given model index.
1181 /// The index should be within [`Self::range()`]
1182 pub fn instance_at(&self, index: usize) -> Option<ItemTreeRc<C>> {
1183 let inner = self.0.inner.borrow();
1184 inner
1185 .instances
1186 .get(index - inner.offset)
1187 .map(|c| c.1.clone().expect("That was updated before!"))
1188 }
1189
1190 /// Return true if the Repeater as empty
1191 pub fn is_empty(&self) -> bool {
1192 self.len() == 0
1193 }
1194
1195 /// Returns a vector containing all instances
1196 pub fn instances_vec(&self) -> Vec<ItemTreeRc<C>> {
1197 self.0.inner.borrow().instances.iter().flat_map(|x| x.1.clone()).collect()
1198 }
1199}
1200
1201impl From<SharedString> for StandardListViewItem {
1202 fn from(value: SharedString) -> Self {
1203 StandardListViewItem { text: value }
1204 }
1205}
1206
1207impl From<&str> for StandardListViewItem {
1208 fn from(value: &str) -> Self {
1209 StandardListViewItem { text: value.into() }
1210 }
1211}
1212
1213#[test]
1214fn test_tracking_model_handle() {
1215 let model: Rc<VecModel<u8>> = Rc::new(Default::default());
1216 let handle = ModelRc::from(model.clone() as Rc<dyn Model<Data = u8>>);
1217 let tracker = Box::pin(crate::properties::PropertyTracker::default());
1218 assert_eq!(
1219 tracker.as_ref().evaluate(|| {
1220 handle.model_tracker().track_row_count_changes();
1221 handle.row_count()
1222 }),
1223 0
1224 );
1225 assert!(!tracker.is_dirty());
1226 model.push(42);
1227 model.push(100);
1228 assert!(tracker.is_dirty());
1229 assert_eq!(
1230 tracker.as_ref().evaluate(|| {
1231 handle.model_tracker().track_row_count_changes();
1232 handle.row_count()
1233 }),
1234 2
1235 );
1236 assert!(!tracker.is_dirty());
1237 model.set_row_data(0, 41);
1238 assert!(!tracker.is_dirty());
1239 model.remove(0);
1240 assert!(tracker.is_dirty());
1241 assert_eq!(
1242 tracker.as_ref().evaluate(|| {
1243 handle.model_tracker().track_row_count_changes();
1244 handle.row_count()
1245 }),
1246 1
1247 );
1248 assert!(!tracker.is_dirty());
1249 model.set_vec(vec![1, 2, 3]);
1250 assert!(tracker.is_dirty());
1251}
1252
1253#[test]
1254fn test_data_tracking() {
1255 let model: Rc<VecModel<u8>> = Rc::new(VecModel::from(vec![0, 1, 2, 3, 4]));
1256 let handle = ModelRc::from(model.clone());
1257 let tracker = Box::pin(crate::properties::PropertyTracker::default());
1258 assert_eq!(
1259 tracker.as_ref().evaluate(|| {
1260 handle.model_tracker().track_row_data_changes(1);
1261 handle.row_data(1).unwrap()
1262 }),
1263 1
1264 );
1265 assert!(!tracker.is_dirty());
1266
1267 model.set_row_data(2, 42);
1268 assert!(!tracker.is_dirty());
1269 model.set_row_data(1, 100);
1270 assert!(tracker.is_dirty());
1271
1272 assert_eq!(
1273 tracker.as_ref().evaluate(|| {
1274 handle.model_tracker().track_row_data_changes(1);
1275 handle.row_data(1).unwrap()
1276 }),
1277 100
1278 );
1279 assert!(!tracker.is_dirty());
1280
1281 // Any changes to rows (even if after tracked rows) for now also marks watched rows as dirty, to
1282 // keep the logic simple.
1283 model.push(200);
1284 assert!(tracker.is_dirty());
1285
1286 assert_eq!(tracker.as_ref().evaluate(|| { handle.row_data_tracked(1).unwrap() }), 100);
1287 assert!(!tracker.is_dirty());
1288
1289 model.insert(0, 255);
1290 assert!(tracker.is_dirty());
1291
1292 model.set_vec(vec![]);
1293 assert!(tracker.is_dirty());
1294}
1295
1296#[test]
1297fn test_vecmodel_set_vec() {
1298 #[derive(Default)]
1299 struct TestView {
1300 // Track the parameters reported by the model (row counts, indices, etc.).
1301 // The last field in the tuple is the row size the model reports at the time
1302 // of callback
1303 changed_rows: RefCell<Vec<(usize, usize)>>,
1304 added_rows: RefCell<Vec<(usize, usize, usize)>>,
1305 removed_rows: RefCell<Vec<(usize, usize, usize)>>,
1306 reset: RefCell<usize>,
1307 model: RefCell<Option<std::rc::Weak<dyn Model<Data = i32>>>>,
1308 }
1309 impl TestView {
1310 fn clear(&self) {
1311 self.changed_rows.borrow_mut().clear();
1312 self.added_rows.borrow_mut().clear();
1313 self.removed_rows.borrow_mut().clear();
1314 *self.reset.borrow_mut() = 0;
1315 }
1316 fn row_count(&self) -> usize {
1317 self.model
1318 .borrow()
1319 .as_ref()
1320 .and_then(|model| model.upgrade())
1321 .map_or(0, |model| model.row_count())
1322 }
1323 }
1324 impl ModelChangeListener for TestView {
1325 fn row_changed(self: Pin<&Self>, row: usize) {
1326 self.changed_rows.borrow_mut().push((row, self.row_count()));
1327 }
1328
1329 fn row_added(self: Pin<&Self>, index: usize, count: usize) {
1330 self.added_rows.borrow_mut().push((index, count, self.row_count()));
1331 }
1332
1333 fn row_removed(self: Pin<&Self>, index: usize, count: usize) {
1334 self.removed_rows.borrow_mut().push((index, count, self.row_count()));
1335 }
1336 fn reset(self: Pin<&Self>) {
1337 *self.reset.borrow_mut() += 1;
1338 }
1339 }
1340
1341 let view = Box::pin(ModelChangeListenerContainer::<TestView>::default());
1342
1343 let model = Rc::new(VecModel::from(vec![1i32, 2, 3, 4]));
1344 model.model_tracker().attach_peer(Pin::as_ref(&view).model_peer());
1345 *view.model.borrow_mut() =
1346 Some(std::rc::Rc::downgrade(&(model.clone() as Rc<dyn Model<Data = i32>>)));
1347
1348 model.push(5);
1349 assert!(view.changed_rows.borrow().is_empty());
1350 assert_eq!(&*view.added_rows.borrow(), &[(4, 1, 5)]);
1351 assert!(view.removed_rows.borrow().is_empty());
1352 assert_eq!(*view.reset.borrow(), 0);
1353 view.clear();
1354
1355 model.set_vec(vec![6, 7, 8]);
1356 assert!(view.changed_rows.borrow().is_empty());
1357 assert!(view.added_rows.borrow().is_empty());
1358 assert!(view.removed_rows.borrow().is_empty());
1359 assert_eq!(*view.reset.borrow(), 1);
1360 view.clear();
1361
1362 model.extend_from_slice(&[9, 10, 11]);
1363 assert!(view.changed_rows.borrow().is_empty());
1364 assert_eq!(&*view.added_rows.borrow(), &[(3, 3, 6)]);
1365 assert!(view.removed_rows.borrow().is_empty());
1366 assert_eq!(*view.reset.borrow(), 0);
1367 view.clear();
1368
1369 model.extend([12, 13]);
1370 assert!(view.changed_rows.borrow().is_empty());
1371 assert_eq!(&*view.added_rows.borrow(), &[(6, 2, 8)]);
1372 assert!(view.removed_rows.borrow().is_empty());
1373 assert_eq!(*view.reset.borrow(), 0);
1374 view.clear();
1375
1376 assert_eq!(model.iter().collect::<Vec<_>>(), vec![6, 7, 8, 9, 10, 11, 12, 13]);
1377}
1378