| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | //! This module contains the implementation of the model change tracking. |
| 5 | |
| 6 | // Safety: we use pointer to ModelChangeListenerContainer in the DependencyList, |
| 7 | // but the Drop of the ModelChangeListenerContainer will remove them from the list |
| 8 | // so it will not be accessed after it is dropped |
| 9 | #![allow (unsafe_code)] |
| 10 | |
| 11 | use super::*; |
| 12 | use crate::properties::dependency_tracker::DependencyNode; |
| 13 | |
| 14 | type DependencyListHead = |
| 15 | crate::properties::dependency_tracker::DependencyListHead<*const dyn ModelChangeListener>; |
| 16 | |
| 17 | /// Represent a handle to a view that listens to changes to a model. |
| 18 | /// |
| 19 | /// One should normally not use this class directly, it is just |
| 20 | /// used internally by via [`ModelTracker::attach_peer`] and [`ModelNotify`] |
| 21 | #[derive (Clone)] |
| 22 | pub struct ModelPeer<'a> { |
| 23 | inner: Pin<&'a DependencyNode<*const dyn ModelChangeListener>>, |
| 24 | } |
| 25 | |
| 26 | #[pin_project ] |
| 27 | #[derive (Default)] |
| 28 | struct ModelNotifyInner { |
| 29 | #[pin] |
| 30 | model_row_count_dirty_property: Property<()>, |
| 31 | #[pin] |
| 32 | model_row_data_dirty_property: Property<()>, |
| 33 | #[pin] |
| 34 | peers: DependencyListHead, |
| 35 | // Sorted list of rows that track_row_data_changes() was called for |
| 36 | tracked_rows: RefCell<Vec<usize>>, |
| 37 | } |
| 38 | |
| 39 | /// Dispatch notifications from a [`Model`] to one or several [`ModelPeer`]. |
| 40 | /// Typically, you would want to put this in the implementation of the Model |
| 41 | #[derive (Default)] |
| 42 | pub struct ModelNotify { |
| 43 | inner: OnceCell<Pin<Box<ModelNotifyInner>>>, |
| 44 | } |
| 45 | |
| 46 | impl ModelNotify { |
| 47 | fn inner(&self) -> Pin<&ModelNotifyInner> { |
| 48 | self.inner.get_or_init(|| Box::pin(ModelNotifyInner::default())).as_ref() |
| 49 | } |
| 50 | |
| 51 | /// Notify the peers that a specific row was changed |
| 52 | pub fn row_changed(&self, row: usize) { |
| 53 | if let Some(inner) = self.inner.get() { |
| 54 | if inner.tracked_rows.borrow().binary_search(&row).is_ok() { |
| 55 | inner.model_row_data_dirty_property.mark_dirty(); |
| 56 | } |
| 57 | inner.as_ref().project_ref().peers.for_each(|p| { |
| 58 | // Safety: The peers contain a list of pinned ModelChangedListener |
| 59 | unsafe { Pin::new_unchecked(&**p) }.row_changed(row) |
| 60 | }) |
| 61 | } |
| 62 | } |
| 63 | /// Notify the peers that rows were added |
| 64 | pub fn row_added(&self, index: usize, count: usize) { |
| 65 | if let Some(inner) = self.inner.get() { |
| 66 | inner.model_row_count_dirty_property.mark_dirty(); |
| 67 | inner.tracked_rows.borrow_mut().clear(); |
| 68 | inner.model_row_data_dirty_property.mark_dirty(); |
| 69 | inner.as_ref().project_ref().peers.for_each(|p| { |
| 70 | // Safety: The peers contain a list of pinned ModelChangedListener |
| 71 | unsafe { Pin::new_unchecked(&**p) }.row_added(index, count) |
| 72 | }) |
| 73 | } |
| 74 | } |
| 75 | /// Notify the peers that rows were removed |
| 76 | pub fn row_removed(&self, index: usize, count: usize) { |
| 77 | if let Some(inner) = self.inner.get() { |
| 78 | inner.model_row_count_dirty_property.mark_dirty(); |
| 79 | inner.tracked_rows.borrow_mut().clear(); |
| 80 | inner.model_row_data_dirty_property.mark_dirty(); |
| 81 | inner.as_ref().project_ref().peers.for_each(|p| { |
| 82 | // Safety: The peers contain a list of pinned ModelChangedListener |
| 83 | unsafe { Pin::new_unchecked(&**p) }.row_removed(index, count) |
| 84 | }) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /// Notify the peer that the model has been changed in some way and |
| 89 | /// everything needs to be reloaded |
| 90 | pub fn reset(&self) { |
| 91 | if let Some(inner) = self.inner.get() { |
| 92 | inner.model_row_count_dirty_property.mark_dirty(); |
| 93 | inner.tracked_rows.borrow_mut().clear(); |
| 94 | inner.model_row_data_dirty_property.mark_dirty(); |
| 95 | inner.as_ref().project_ref().peers.for_each(|p| { |
| 96 | // Safety: The peers contain a list of pinned ModelChangedListener |
| 97 | unsafe { Pin::new_unchecked(&**p) }.reset() |
| 98 | }) |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | impl ModelTracker for ModelNotify { |
| 104 | /// Attach one peer. The peer will be notified when the model changes |
| 105 | fn attach_peer(&self, peer: ModelPeer) { |
| 106 | self.inner().project_ref().peers.append(peer.inner) |
| 107 | } |
| 108 | |
| 109 | fn track_row_count_changes(&self) { |
| 110 | self.inner().project_ref().model_row_count_dirty_property.get(); |
| 111 | } |
| 112 | |
| 113 | fn track_row_data_changes(&self, row: usize) { |
| 114 | if crate::properties::is_currently_tracking() { |
| 115 | let inner = self.inner().project_ref(); |
| 116 | |
| 117 | let mut tracked_rows = inner.tracked_rows.borrow_mut(); |
| 118 | if let Err(insertion_point) = tracked_rows.binary_search(&row) { |
| 119 | tracked_rows.insert(insertion_point, row); |
| 120 | } |
| 121 | |
| 122 | inner.model_row_data_dirty_property.get(); |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | pub trait ModelChangeListener { |
| 128 | fn row_changed(self: Pin<&Self>, row: usize); |
| 129 | fn row_added(self: Pin<&Self>, index: usize, count: usize); |
| 130 | fn row_removed(self: Pin<&Self>, index: usize, count: usize); |
| 131 | fn reset(self: Pin<&Self>); |
| 132 | } |
| 133 | |
| 134 | #[pin_project (PinnedDrop)] |
| 135 | #[derive (Default, derive_more::Deref)] |
| 136 | /// This is a structure that contains a T which implements [`ModelChangeListener`] |
| 137 | /// and can provide a [`ModelPeer`] for it when pinned. |
| 138 | pub struct ModelChangeListenerContainer<T: ModelChangeListener> { |
| 139 | /// Will be initialized when the ModelPeer is initialized. |
| 140 | /// The DependencyNode points to data. |
| 141 | peer: OnceCell<DependencyNode<*const dyn ModelChangeListener>>, |
| 142 | |
| 143 | #[pin] |
| 144 | #[deref] |
| 145 | data: T, |
| 146 | } |
| 147 | |
| 148 | #[pin_project::pinned_drop ] |
| 149 | impl<T: ModelChangeListener> PinnedDrop for ModelChangeListenerContainer<T> { |
| 150 | fn drop(self: Pin<&mut Self>) { |
| 151 | if let Some(peer: &DependencyNode<*const dyn ModelChangeListener>) = self.peer.get() { |
| 152 | peer.remove(); |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | impl<T: ModelChangeListener + 'static> ModelChangeListenerContainer<T> { |
| 158 | pub fn new(data: T) -> Self { |
| 159 | Self { peer: Default::default(), data } |
| 160 | } |
| 161 | |
| 162 | pub fn model_peer(self: Pin<&Self>) -> ModelPeer { |
| 163 | let peer: &DependencyNode<*const dyn ModelChangeListener> = self.get_ref().peer.get_or_init(|| { |
| 164 | //Safety: self.data and self.peer have the same lifetime, so the pointer stays valid |
| 165 | DependencyNode::new( |
| 166 | (&self.data) as &dyn ModelChangeListener as *const dyn ModelChangeListener, |
| 167 | ) |
| 168 | }); |
| 169 | |
| 170 | // Safety: `peer` is pinned because `self` is pinned and it is a projection, but pin_project don't go through the OnceCell |
| 171 | let peer: Pin<&DependencyNode<*const …>> = unsafe { Pin::new_unchecked(pointer:peer) }; |
| 172 | |
| 173 | ModelPeer { inner: peer } |
| 174 | } |
| 175 | |
| 176 | pub fn get(self: Pin<&Self>) -> Pin<&T> { |
| 177 | self.project_ref().data |
| 178 | } |
| 179 | } |
| 180 | |