diff --git a/api/sixtyfps-cpp/include/sixtyfps.h b/api/sixtyfps-cpp/include/sixtyfps.h index b182fb0df..ad2c32427 100644 --- a/api/sixtyfps-cpp/include/sixtyfps.h +++ b/api/sixtyfps-cpp/include/sixtyfps.h @@ -431,11 +431,17 @@ public: /// Returns the data for a particular row. This function should be called with `row < /// row_count()`. virtual ModelData row_data(int i) const = 0; - /// Sets the data for a particular row. This function should be called with `row < row_count()`. - /// If the model cannot support data changes, then it is ok to do nothing (default - /// implementation). If the model can update the data, the implementation should also call - /// row_changed. - virtual void set_row_data(int, const ModelData &) {}; + /// Sets the data for a particular row. + /// + /// This function should only be called with `row < row_count()`. + /// + /// 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 `row_changed` + virtual void set_row_data(int, const ModelData &) { + std::cerr << "Model::set_row_data was called on a read-only model" << std::endl; + }; /// \private /// Internal function called by the view to register itself diff --git a/sixtyfps_runtime/corelib/model.rs b/sixtyfps_runtime/corelib/model.rs index a71764694..f648a2fa1 100644 --- a/sixtyfps_runtime/corelib/model.rs +++ b/sixtyfps_runtime/corelib/model.rs @@ -64,6 +64,68 @@ impl ModelNotify { } /// A Model is providing Data for the Repeater or ListView elements of the `.60` 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. +/// +/// ## Example +/// +/// As an example, let's see the implementation of [`VecModel`]. +/// +/// ``` +/// # use sixtyfps_corelib::model::{Model, ModelNotify, ModelPeer}; +/// 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) -> Self::Data { +/// self.array.borrow()[row].clone() +/// } +/// +/// 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 attach_peer(&self, peer: ModelPeer) { +/// // simply forward to ModelNotify::attach +/// self.notify.attach(peer); +/// } +/// +/// 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 row and each row has this data type Data; @@ -71,11 +133,23 @@ pub trait Model { fn row_count(&self) -> usize; /// Returns the data for a particular row. This function should be called with `row < row_count()`. fn row_data(&self, row: usize) -> Self::Data; - /// Sets the data for a particular row. This function should be called with `row < row_count()`. - /// If the model cannot support data changes, then it is ok to do nothing (default implementation). - /// If the model can update the data, it should also call row_changed on its internal `ModelNotify`. - fn set_row_data(&self, _row: usize, _data: Self::Data) {} - /// Should forward to the internal [`ModelNotify::attach`] + /// 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 [`ModelNofity::row_changed`] on its + /// internal [`ModelNotify`]. + fn set_row_data(&self, _row: usize, _data: Self::Data) { + 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 forward to [`ModelNotify::attach`] fn attach_peer(&self, peer: ModelPeer); /// Returns an iterator visiting all elements of the model. @@ -139,7 +213,7 @@ impl<'a, T> Iterator for ModelIterator<'a, T> { impl<'a, T> ExactSizeIterator for ModelIterator<'a, T> {} -/// A model backed by a SharedVector +/// A model backed by a `Vec` #[derive(Default)] pub struct VecModel { array: RefCell>, diff --git a/sixtyfps_runtime/interpreter/value_model.rs b/sixtyfps_runtime/interpreter/value_model.rs index ba7c410fe..cb3e4924b 100644 --- a/sixtyfps_runtime/interpreter/value_model.rs +++ b/sixtyfps_runtime/interpreter/value_model.rs @@ -67,7 +67,7 @@ impl Model for ValueModel { self.notify.row_changed(row) } Value::Model(model_ptr) => model_ptr.set_row_data(row, data), - _ => println!("Value of model cannot be change"), + _ => eprintln!("Trying to change the value of a read-only integer model."), } }