| 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 | /*! |
| 5 | Property binding engine. |
| 6 | |
| 7 | The current implementation uses lots of heap allocation but that can be optimized later using |
| 8 | thin dst container, and intrusive linked list |
| 9 | */ |
| 10 | |
| 11 | // cSpell: ignore rustflags |
| 12 | |
| 13 | #![allow (unsafe_code)] |
| 14 | #![warn (missing_docs)] |
| 15 | |
| 16 | /// A singled linked list whose nodes are pinned |
| 17 | mod single_linked_list_pin { |
| 18 | #![allow (unsafe_code)] |
| 19 | use alloc::boxed::Box; |
| 20 | use core::pin::Pin; |
| 21 | |
| 22 | type NodePtr<T> = Option<Pin<Box<SingleLinkedListPinNode<T>>>>; |
| 23 | struct SingleLinkedListPinNode<T> { |
| 24 | next: NodePtr<T>, |
| 25 | value: T, |
| 26 | } |
| 27 | |
| 28 | pub struct SingleLinkedListPinHead<T>(NodePtr<T>); |
| 29 | impl<T> Default for SingleLinkedListPinHead<T> { |
| 30 | fn default() -> Self { |
| 31 | Self(None) |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | impl<T> Drop for SingleLinkedListPinHead<T> { |
| 36 | fn drop(&mut self) { |
| 37 | // Use a loop instead of relying on the Drop of NodePtr to avoid recursion |
| 38 | while let Some(mut x) = core::mem::take(&mut self.0) { |
| 39 | // Safety: we don't touch the `x.value` which is the one protected by the Pin |
| 40 | self.0 = core::mem::take(unsafe { &mut Pin::get_unchecked_mut(x.as_mut()).next }); |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | impl<T> SingleLinkedListPinHead<T> { |
| 46 | pub fn push_front(&mut self, value: T) -> Pin<&T> { |
| 47 | self.0 = Some(Box::pin(SingleLinkedListPinNode { next: self.0.take(), value })); |
| 48 | // Safety: we can project from SingleLinkedListPinNode |
| 49 | unsafe { Pin::new_unchecked(&self.0.as_ref().unwrap().value) } |
| 50 | } |
| 51 | |
| 52 | #[allow (unused)] |
| 53 | pub fn iter(&self) -> impl Iterator<Item = Pin<&T>> { |
| 54 | struct I<'a, T>(&'a NodePtr<T>); |
| 55 | |
| 56 | impl<'a, T> Iterator for I<'a, T> { |
| 57 | type Item = Pin<&'a T>; |
| 58 | fn next(&mut self) -> Option<Self::Item> { |
| 59 | if let Some(x) = &self.0 { |
| 60 | let r = unsafe { Pin::new_unchecked(&x.value) }; |
| 61 | self.0 = &x.next; |
| 62 | Some(r) |
| 63 | } else { |
| 64 | None |
| 65 | } |
| 66 | } |
| 67 | } |
| 68 | I(&self.0) |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | #[test ] |
| 73 | fn test_list() { |
| 74 | let mut head = SingleLinkedListPinHead::default(); |
| 75 | head.push_front(1); |
| 76 | head.push_front(2); |
| 77 | head.push_front(3); |
| 78 | assert_eq!( |
| 79 | head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::<std::vec::Vec<i32>>(), |
| 80 | std::vec![3, 2, 1] |
| 81 | ); |
| 82 | } |
| 83 | #[test ] |
| 84 | fn big_list() { |
| 85 | // should not stack overflow |
| 86 | let mut head = SingleLinkedListPinHead::default(); |
| 87 | for x in 0..100000 { |
| 88 | head.push_front(x); |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | pub(crate) mod dependency_tracker { |
| 94 | //! This module contains an implementation of a double linked list that can be used |
| 95 | //! to track dependency, such that when a node is dropped, the nodes are automatically |
| 96 | //! removed from the list. |
| 97 | //! This is unsafe to use for various reason, so it is kept internal. |
| 98 | |
| 99 | use core::cell::Cell; |
| 100 | use core::pin::Pin; |
| 101 | |
| 102 | #[repr (transparent)] |
| 103 | pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>); |
| 104 | |
| 105 | impl<T> Default for DependencyListHead<T> { |
| 106 | fn default() -> Self { |
| 107 | Self(Cell::new(core::ptr::null())) |
| 108 | } |
| 109 | } |
| 110 | impl<T> Drop for DependencyListHead<T> { |
| 111 | fn drop(&mut self) { |
| 112 | unsafe { DependencyListHead::drop(self as *mut Self) }; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | impl<T> DependencyListHead<T> { |
| 117 | pub unsafe fn mem_move(from: *mut Self, to: *mut Self) { |
| 118 | (*to).0.set((*from).0.get()); |
| 119 | if let Some(next) = (*from).0.get().as_ref() { |
| 120 | debug_assert_eq!(from as *const _, next.prev.get() as *const _); |
| 121 | next.debug_assert_valid(); |
| 122 | next.prev.set(to as *const _); |
| 123 | next.debug_assert_valid(); |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | /// Swap two list head |
| 128 | pub fn swap(from: Pin<&Self>, to: Pin<&Self>) { |
| 129 | Cell::swap(&from.0, &to.0); |
| 130 | unsafe { |
| 131 | if let Some(n) = from.0.get().as_ref() { |
| 132 | debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _); |
| 133 | n.prev.set(&from.0 as *const _); |
| 134 | n.debug_assert_valid(); |
| 135 | } |
| 136 | |
| 137 | if let Some(n) = to.0.get().as_ref() { |
| 138 | debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _); |
| 139 | n.prev.set(&to.0 as *const _); |
| 140 | n.debug_assert_valid(); |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /// Return true is the list is empty |
| 146 | pub fn is_empty(&self) -> bool { |
| 147 | self.0.get().is_null() |
| 148 | } |
| 149 | |
| 150 | /// Remove all the nodes from the list; |
| 151 | pub fn clear(self: Pin<&Self>) { |
| 152 | unsafe { |
| 153 | if let Some(n) = self.0.get().as_ref() { |
| 154 | n.debug_assert_valid(); |
| 155 | n.prev.set(core::ptr::null()); |
| 156 | } |
| 157 | } |
| 158 | self.0.set(core::ptr::null()); |
| 159 | } |
| 160 | |
| 161 | pub unsafe fn drop(_self: *mut Self) { |
| 162 | if let Some(next) = (*_self).0.get().as_ref() { |
| 163 | debug_assert_eq!(_self as *const _, next.prev.get() as *const _); |
| 164 | next.debug_assert_valid(); |
| 165 | next.prev.set(core::ptr::null()); |
| 166 | next.debug_assert_valid(); |
| 167 | } |
| 168 | } |
| 169 | pub fn append(&self, node: Pin<&DependencyNode<T>>) { |
| 170 | unsafe { |
| 171 | node.remove(); |
| 172 | node.debug_assert_valid(); |
| 173 | let old = self.0.get(); |
| 174 | if let Some(x) = old.as_ref() { |
| 175 | x.debug_assert_valid(); |
| 176 | } |
| 177 | self.0.set(node.get_ref() as *const DependencyNode<_>); |
| 178 | node.next.set(old); |
| 179 | node.prev.set(&self.0 as *const _); |
| 180 | if let Some(old) = old.as_ref() { |
| 181 | old.prev.set((&node.next) as *const _); |
| 182 | old.debug_assert_valid(); |
| 183 | } |
| 184 | node.debug_assert_valid(); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | pub fn for_each(&self, mut f: impl FnMut(&T)) { |
| 189 | unsafe { |
| 190 | let mut next = self.0.get(); |
| 191 | while let Some(node) = next.as_ref() { |
| 192 | node.debug_assert_valid(); |
| 193 | next = node.next.get(); |
| 194 | f(&node.binding); |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /// The node is owned by the binding; so the binding is always valid |
| 201 | /// The next and pref |
| 202 | pub struct DependencyNode<T> { |
| 203 | next: Cell<*const DependencyNode<T>>, |
| 204 | /// This is either null, or a pointer to a pointer to ourself |
| 205 | prev: Cell<*const Cell<*const DependencyNode<T>>>, |
| 206 | binding: T, |
| 207 | } |
| 208 | |
| 209 | impl<T> DependencyNode<T> { |
| 210 | pub fn new(binding: T) -> Self { |
| 211 | Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding } |
| 212 | } |
| 213 | |
| 214 | /// Assert that the invariant of `next` and `prev` are met. |
| 215 | pub fn debug_assert_valid(&self) { |
| 216 | unsafe { |
| 217 | debug_assert!( |
| 218 | self.prev.get().is_null() |
| 219 | || (*self.prev.get()).get() == self as *const DependencyNode<T> |
| 220 | ); |
| 221 | debug_assert!( |
| 222 | self.next.get().is_null() |
| 223 | || (*self.next.get()).prev.get() |
| 224 | == (&self.next) as *const Cell<*const DependencyNode<T>> |
| 225 | ); |
| 226 | // infinite loop? |
| 227 | debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>); |
| 228 | debug_assert_ne!( |
| 229 | self.prev.get(), |
| 230 | (&self.next) as *const Cell<*const DependencyNode<T>> |
| 231 | ); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | pub fn remove(&self) { |
| 236 | self.debug_assert_valid(); |
| 237 | unsafe { |
| 238 | if let Some(prev) = self.prev.get().as_ref() { |
| 239 | prev.set(self.next.get()); |
| 240 | } |
| 241 | if let Some(next) = self.next.get().as_ref() { |
| 242 | next.debug_assert_valid(); |
| 243 | next.prev.set(self.prev.get()); |
| 244 | next.debug_assert_valid(); |
| 245 | } |
| 246 | } |
| 247 | self.prev.set(core::ptr::null()); |
| 248 | self.next.set(core::ptr::null()); |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | impl<T> Drop for DependencyNode<T> { |
| 253 | fn drop(&mut self) { |
| 254 | self.remove(); |
| 255 | } |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>; |
| 260 | type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>; |
| 261 | |
| 262 | use alloc::boxed::Box; |
| 263 | use alloc::rc::Rc; |
| 264 | use core::cell::{Cell, RefCell, UnsafeCell}; |
| 265 | use core::marker::PhantomPinned; |
| 266 | use core::pin::Pin; |
| 267 | |
| 268 | /// if a DependencyListHead points to that value, it is because the property is actually |
| 269 | /// constant and cannot have dependencies |
| 270 | static CONSTANT_PROPERTY_SENTINEL: u32 = 0; |
| 271 | |
| 272 | /// The return value of a binding |
| 273 | #[derive (Copy, Clone, Debug, Eq, PartialEq)] |
| 274 | enum BindingResult { |
| 275 | /// The binding is a normal binding, and we keep it to re-evaluate it once it is dirty |
| 276 | KeepBinding, |
| 277 | /// The value of the property is now constant after the binding was evaluated, so |
| 278 | /// the binding can be removed. |
| 279 | RemoveBinding, |
| 280 | } |
| 281 | |
| 282 | struct BindingVTable { |
| 283 | drop: unsafe fn(_self: *mut BindingHolder), |
| 284 | evaluate: unsafe fn(_self: *mut BindingHolder, value: *mut ()) -> BindingResult, |
| 285 | mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool), |
| 286 | intercept_set: unsafe fn(_self: *const BindingHolder, value: *const ()) -> bool, |
| 287 | intercept_set_binding: |
| 288 | unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool, |
| 289 | } |
| 290 | |
| 291 | /// A binding trait object can be used to dynamically produces values for a property. |
| 292 | /// |
| 293 | /// # Safety |
| 294 | /// |
| 295 | /// IS_TWO_WAY_BINDING cannot be true if Self is not a TwoWayBinding |
| 296 | unsafe trait BindingCallable { |
| 297 | /// This function is called by the property to evaluate the binding and produce a new value. The |
| 298 | /// previous property value is provided in the value parameter. |
| 299 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult; |
| 300 | |
| 301 | /// This function is used to notify the binding that one of the dependencies was changed |
| 302 | /// and therefore this binding may evaluate to a different value, too. |
| 303 | fn mark_dirty(self: Pin<&Self>) {} |
| 304 | |
| 305 | /// Allow the binding to intercept what happens when the value is set. |
| 306 | /// The default implementation returns false, meaning the binding will simply be removed and |
| 307 | /// the property will get the new value. |
| 308 | /// When returning true, the call was intercepted and the binding will not be removed, |
| 309 | /// but the property will still have that value |
| 310 | unsafe fn intercept_set(self: Pin<&Self>, _value: *const ()) -> bool { |
| 311 | false |
| 312 | } |
| 313 | |
| 314 | /// Allow the binding to intercept what happens when the value is set. |
| 315 | /// The default implementation returns false, meaning the binding will simply be removed. |
| 316 | /// When returning true, the call was intercepted and the binding will not be removed. |
| 317 | unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool { |
| 318 | false |
| 319 | } |
| 320 | |
| 321 | /// Set to true if and only if Self is a TwoWayBinding<T> |
| 322 | const IS_TWO_WAY_BINDING: bool = false; |
| 323 | } |
| 324 | |
| 325 | unsafe impl<F: Fn(*mut ()) -> BindingResult> BindingCallable for F { |
| 326 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
| 327 | self(value) |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | #[cfg (feature = "std" )] |
| 332 | use std::thread_local; |
| 333 | #[cfg (feature = "std" )] |
| 334 | scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Option<Pin<&'a BindingHolder>>); |
| 335 | |
| 336 | #[cfg (all(not(feature = "std" ), feature = "unsafe-single-threaded" ))] |
| 337 | mod unsafe_single_threaded { |
| 338 | use super::BindingHolder; |
| 339 | use core::cell::Cell; |
| 340 | use core::pin::Pin; |
| 341 | use core::ptr::null; |
| 342 | pub(super) struct FakeThreadStorage(Cell<*const BindingHolder>); |
| 343 | impl FakeThreadStorage { |
| 344 | pub const fn new() -> Self { |
| 345 | Self(Cell::new(null())) |
| 346 | } |
| 347 | pub fn set<T>(&self, value: Option<Pin<&BindingHolder>>, f: impl FnOnce() -> T) -> T { |
| 348 | let old = self.0.replace(value.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
| 349 | let res = f(); |
| 350 | let new = self.0.replace(old); |
| 351 | assert_eq!(new, value.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
| 352 | res |
| 353 | } |
| 354 | pub fn is_set(&self) -> bool { |
| 355 | !self.0.get().is_null() |
| 356 | } |
| 357 | pub fn with<T>(&self, f: impl FnOnce(Option<Pin<&BindingHolder>>) -> T) -> T { |
| 358 | let local = unsafe { self.0.get().as_ref().map(|x| Pin::new_unchecked(x)) }; |
| 359 | let res = f(local); |
| 360 | assert_eq!(self.0.get(), local.map_or(null(), |v| v.get_ref() as *const BindingHolder)); |
| 361 | res |
| 362 | } |
| 363 | } |
| 364 | // Safety: the unsafe_single_threaded feature means we will only be called from a single thread |
| 365 | unsafe impl Send for FakeThreadStorage {} |
| 366 | unsafe impl Sync for FakeThreadStorage {} |
| 367 | } |
| 368 | #[cfg (all(not(feature = "std" ), feature = "unsafe-single-threaded" ))] |
| 369 | static CURRENT_BINDING: unsafe_single_threaded::FakeThreadStorage = |
| 370 | unsafe_single_threaded::FakeThreadStorage::new(); |
| 371 | |
| 372 | /// Evaluate a function, but do not register any property dependencies if that function |
| 373 | /// get the value of properties |
| 374 | pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T { |
| 375 | CURRENT_BINDING.set(t:None, f) |
| 376 | } |
| 377 | |
| 378 | /// Return true if there is currently a binding being evaluated so that access to |
| 379 | /// properties register dependencies to that binding. |
| 380 | pub fn is_currently_tracking() -> bool { |
| 381 | CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x: Option>| x.is_some()) |
| 382 | } |
| 383 | |
| 384 | /// This structure erase the `B` type with a vtable. |
| 385 | #[repr (C)] |
| 386 | struct BindingHolder<B = ()> { |
| 387 | /// Access to the list of binding which depends on this binding |
| 388 | dependencies: Cell<usize>, |
| 389 | /// The binding own the nodes used in the dependencies lists of the properties |
| 390 | /// From which we depend. |
| 391 | dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>, |
| 392 | vtable: &'static BindingVTable, |
| 393 | /// The binding is dirty and need to be re_evaluated |
| 394 | dirty: Cell<bool>, |
| 395 | /// Specify that B is a `TwoWayBinding<T>` |
| 396 | is_two_way_binding: bool, |
| 397 | pinned: PhantomPinned, |
| 398 | #[cfg (slint_debug_property)] |
| 399 | pub debug_name: String, |
| 400 | |
| 401 | binding: B, |
| 402 | } |
| 403 | |
| 404 | impl BindingHolder { |
| 405 | fn register_self_as_dependency( |
| 406 | self: Pin<&Self>, |
| 407 | property_that_will_notify: *mut DependencyListHead, |
| 408 | #[cfg (slint_debug_property)] other_debug_name: &str, |
| 409 | ) { |
| 410 | let node: DependencyNode<*const BindingHolder> = DependencyNode::new(self.get_ref() as *const _); |
| 411 | let mut dep_nodes: SingleLinkedListPinHead> = self.dep_nodes.take(); |
| 412 | let node: Pin<&DependencyNode<*const …>> = dep_nodes.push_front(node); |
| 413 | unsafe { DependencyListHead::append(&*property_that_will_notify, node) } |
| 414 | self.dep_nodes.set(val:dep_nodes); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | fn alloc_binding_holder<B: BindingCallable + 'static>(binding: B) -> *mut BindingHolder { |
| 419 | /// Safety: _self must be a pointer that comes from a `Box<BindingHolder<B>>::into_raw()` |
| 420 | unsafe fn binding_drop<B>(_self: *mut BindingHolder) { |
| 421 | drop(Box::from_raw(_self as *mut BindingHolder<B>)); |
| 422 | } |
| 423 | |
| 424 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
| 425 | /// and value must be a pointer to T |
| 426 | unsafe fn evaluate<B: BindingCallable>( |
| 427 | _self: *mut BindingHolder, |
| 428 | value: *mut (), |
| 429 | ) -> BindingResult { |
| 430 | let pinned_holder = Pin::new_unchecked(&*_self); |
| 431 | CURRENT_BINDING.set(Some(pinned_holder), || { |
| 432 | Pin::new_unchecked(&((*(_self as *mut BindingHolder<B>)).binding)).evaluate(value) |
| 433 | }) |
| 434 | } |
| 435 | |
| 436 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
| 437 | unsafe fn mark_dirty<B: BindingCallable>(_self: *const BindingHolder, _: bool) { |
| 438 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() |
| 439 | } |
| 440 | |
| 441 | /// Safety: _self must be a pointer to a `BindingHolder<B>` |
| 442 | unsafe fn intercept_set<B: BindingCallable>( |
| 443 | _self: *const BindingHolder, |
| 444 | value: *const (), |
| 445 | ) -> bool { |
| 446 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).intercept_set(value) |
| 447 | } |
| 448 | |
| 449 | unsafe fn intercept_set_binding<B: BindingCallable>( |
| 450 | _self: *const BindingHolder, |
| 451 | new_binding: *mut BindingHolder, |
| 452 | ) -> bool { |
| 453 | Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)) |
| 454 | .intercept_set_binding(new_binding) |
| 455 | } |
| 456 | |
| 457 | trait HasBindingVTable { |
| 458 | const VT: &'static BindingVTable; |
| 459 | } |
| 460 | impl<B: BindingCallable> HasBindingVTable for B { |
| 461 | const VT: &'static BindingVTable = &BindingVTable { |
| 462 | drop: binding_drop::<B>, |
| 463 | evaluate: evaluate::<B>, |
| 464 | mark_dirty: mark_dirty::<B>, |
| 465 | intercept_set: intercept_set::<B>, |
| 466 | intercept_set_binding: intercept_set_binding::<B>, |
| 467 | }; |
| 468 | } |
| 469 | |
| 470 | let holder: BindingHolder<B> = BindingHolder { |
| 471 | dependencies: Cell::new(0), |
| 472 | dep_nodes: Default::default(), |
| 473 | vtable: <B as HasBindingVTable>::VT, |
| 474 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
| 475 | is_two_way_binding: B::IS_TWO_WAY_BINDING, |
| 476 | pinned: PhantomPinned, |
| 477 | #[cfg (slint_debug_property)] |
| 478 | debug_name: Default::default(), |
| 479 | binding, |
| 480 | }; |
| 481 | Box::into_raw(Box::new(holder)) as *mut BindingHolder |
| 482 | } |
| 483 | |
| 484 | #[repr (transparent)] |
| 485 | #[derive (Default)] |
| 486 | struct PropertyHandle { |
| 487 | /// The handle can either be a pointer to a binding, or a pointer to the list of dependent properties. |
| 488 | /// The two least significant bit of the pointer are flags, as the pointer will be aligned. |
| 489 | /// The least significant bit (`0b01`) tells that the binding is borrowed. So no two reference to the |
| 490 | /// binding exist at the same time. |
| 491 | /// The second to last bit (`0b10`) tells that the pointer points to a binding. Otherwise, it is the head |
| 492 | /// node of the linked list of dependent binding |
| 493 | handle: Cell<usize>, |
| 494 | } |
| 495 | |
| 496 | impl core::fmt::Debug for PropertyHandle { |
| 497 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 498 | let handle: usize = self.handle.get(); |
| 499 | write!( |
| 500 | f, |
| 501 | "PropertyHandle {{ handle: 0x {:x}, locked: {}, binding: {} }}" , |
| 502 | handle & !0b11, |
| 503 | (handle & 0b01) == 0b01, |
| 504 | (handle & 0b10) == 0b10 |
| 505 | ) |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | impl PropertyHandle { |
| 510 | /// The lock flag specify that we can get reference to the Cell or unsafe cell |
| 511 | fn lock_flag(&self) -> bool { |
| 512 | self.handle.get() & 0b1 == 1 |
| 513 | } |
| 514 | /// Sets the lock_flag. |
| 515 | /// Safety: the lock flag must not be unset if there exist reference to what's inside the cell |
| 516 | unsafe fn set_lock_flag(&self, set: bool) { |
| 517 | self.handle.set(if set { self.handle.get() | 0b1 } else { self.handle.get() & !0b1 }) |
| 518 | } |
| 519 | |
| 520 | /// Access the value. |
| 521 | /// Panics if the function try to recursively access the value |
| 522 | fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R { |
| 523 | assert!(!self.lock_flag(), "Recursion detected" ); |
| 524 | unsafe { |
| 525 | self.set_lock_flag(true); |
| 526 | scopeguard::defer! { self.set_lock_flag(false); } |
| 527 | let handle = self.handle.get(); |
| 528 | let binding = if handle & 0b10 == 0b10 { |
| 529 | Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder))) |
| 530 | } else { |
| 531 | None |
| 532 | }; |
| 533 | f(binding) |
| 534 | } |
| 535 | } |
| 536 | |
| 537 | fn remove_binding(&self) { |
| 538 | assert!(!self.lock_flag(), "Recursion detected" ); |
| 539 | let val = self.handle.get(); |
| 540 | if val & 0b10 == 0b10 { |
| 541 | unsafe { |
| 542 | self.set_lock_flag(true); |
| 543 | let binding = (val & !0b11) as *mut BindingHolder; |
| 544 | let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; |
| 545 | if (*binding).dependencies.get() == const_sentinel { |
| 546 | self.handle.set(const_sentinel); |
| 547 | (*binding).dependencies.set(0); |
| 548 | } else { |
| 549 | DependencyListHead::mem_move( |
| 550 | (*binding).dependencies.as_ptr() as *mut DependencyListHead, |
| 551 | self.handle.as_ptr() as *mut DependencyListHead, |
| 552 | ); |
| 553 | } |
| 554 | ((*binding).vtable.drop)(binding); |
| 555 | } |
| 556 | debug_assert!(self.handle.get() & 0b11 == 0); |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | /// Safety: the BindingCallable must be valid for the type of this property |
| 561 | unsafe fn set_binding<B: BindingCallable + 'static>( |
| 562 | &self, |
| 563 | binding: B, |
| 564 | #[cfg (slint_debug_property)] debug_name: &str, |
| 565 | ) { |
| 566 | let binding = alloc_binding_holder::<B>(binding); |
| 567 | #[cfg (slint_debug_property)] |
| 568 | { |
| 569 | (*binding).debug_name = debug_name.into(); |
| 570 | } |
| 571 | self.set_binding_impl(binding); |
| 572 | } |
| 573 | |
| 574 | /// Implementation of Self::set_binding. |
| 575 | fn set_binding_impl(&self, binding: *mut BindingHolder) { |
| 576 | let previous_binding_intercepted = self.access(|b| { |
| 577 | b.is_some_and(|b| unsafe { |
| 578 | // Safety: b is a BindingHolder<T> |
| 579 | (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding) |
| 580 | }) |
| 581 | }); |
| 582 | |
| 583 | if previous_binding_intercepted { |
| 584 | return; |
| 585 | } |
| 586 | |
| 587 | self.remove_binding(); |
| 588 | debug_assert!((binding as usize) & 0b11 == 0); |
| 589 | debug_assert!(self.handle.get() & 0b11 == 0); |
| 590 | let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize; |
| 591 | let is_constant = self.handle.get() == const_sentinel; |
| 592 | unsafe { |
| 593 | if is_constant { |
| 594 | (*binding).dependencies.set(const_sentinel); |
| 595 | } else { |
| 596 | DependencyListHead::mem_move( |
| 597 | self.handle.as_ptr() as *mut DependencyListHead, |
| 598 | (*binding).dependencies.as_ptr() as *mut DependencyListHead, |
| 599 | ); |
| 600 | } |
| 601 | } |
| 602 | self.handle.set((binding as usize) | 0b10); |
| 603 | if !is_constant { |
| 604 | self.mark_dirty( |
| 605 | #[cfg (slint_debug_property)] |
| 606 | "" , |
| 607 | ); |
| 608 | } |
| 609 | } |
| 610 | |
| 611 | fn dependencies(&self) -> *mut DependencyListHead { |
| 612 | assert!(!self.lock_flag(), "Recursion detected" ); |
| 613 | if (self.handle.get() & 0b10) != 0 { |
| 614 | self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead) |
| 615 | } else { |
| 616 | self.handle.as_ptr() as *mut DependencyListHead |
| 617 | } |
| 618 | } |
| 619 | |
| 620 | // `value` is the content of the unsafe cell and will be only dereferenced if the |
| 621 | // handle is not locked. (Upholding the requirements of UnsafeCell) |
| 622 | unsafe fn update<T>(&self, value: *mut T) { |
| 623 | let remove = self.access(|binding| { |
| 624 | if let Some(mut binding) = binding { |
| 625 | if binding.dirty.get() { |
| 626 | // clear all the nodes so that we can start from scratch |
| 627 | binding.dep_nodes.set(Default::default()); |
| 628 | let r = (binding.vtable.evaluate)( |
| 629 | binding.as_mut().get_unchecked_mut() as *mut BindingHolder, |
| 630 | value as *mut (), |
| 631 | ); |
| 632 | binding.dirty.set(false); |
| 633 | if r == BindingResult::RemoveBinding { |
| 634 | return true; |
| 635 | } |
| 636 | } |
| 637 | } |
| 638 | false |
| 639 | }); |
| 640 | if remove { |
| 641 | self.remove_binding() |
| 642 | } |
| 643 | } |
| 644 | |
| 645 | /// Register this property as a dependency to the current binding being evaluated |
| 646 | fn register_as_dependency_to_current_binding( |
| 647 | self: Pin<&Self>, |
| 648 | #[cfg (slint_debug_property)] debug_name: &str, |
| 649 | ) { |
| 650 | if CURRENT_BINDING.is_set() { |
| 651 | CURRENT_BINDING.with(|cur_binding| { |
| 652 | if let Some(cur_binding) = cur_binding { |
| 653 | let dependencies = self.dependencies(); |
| 654 | if !core::ptr::eq( |
| 655 | unsafe { *(dependencies as *mut *const u32) }, |
| 656 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 657 | ) { |
| 658 | cur_binding.register_self_as_dependency( |
| 659 | dependencies, |
| 660 | #[cfg (slint_debug_property)] |
| 661 | debug_name, |
| 662 | ); |
| 663 | } |
| 664 | } |
| 665 | }); |
| 666 | } |
| 667 | } |
| 668 | |
| 669 | fn mark_dirty(&self, #[cfg (slint_debug_property)] debug_name: &str) { |
| 670 | #[cfg (not(slint_debug_property))] |
| 671 | let debug_name = "" ; |
| 672 | unsafe { |
| 673 | let dependencies = self.dependencies(); |
| 674 | assert!( |
| 675 | !core::ptr::eq( |
| 676 | *(dependencies as *mut *const u32), |
| 677 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 678 | ), |
| 679 | "Constant property being changed {debug_name}" |
| 680 | ); |
| 681 | mark_dependencies_dirty(dependencies) |
| 682 | }; |
| 683 | } |
| 684 | |
| 685 | fn set_constant(&self) { |
| 686 | unsafe { |
| 687 | let dependencies = self.dependencies(); |
| 688 | if !core::ptr::eq( |
| 689 | *(dependencies as *mut *const u32), |
| 690 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 691 | ) { |
| 692 | DependencyListHead::drop(dependencies); |
| 693 | *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 |
| 694 | } |
| 695 | } |
| 696 | } |
| 697 | } |
| 698 | |
| 699 | impl Drop for PropertyHandle { |
| 700 | fn drop(&mut self) { |
| 701 | self.remove_binding(); |
| 702 | debug_assert!(self.handle.get() & 0b11 == 0); |
| 703 | if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 { |
| 704 | unsafe { |
| 705 | DependencyListHead::drop(self.handle.as_ptr() as *mut _); |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | /// Safety: the dependency list must be valid and consistent |
| 712 | unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) { |
| 713 | debug_assert!(!core::ptr::eq( |
| 714 | *(dependencies as *mut *const u32), |
| 715 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 716 | )); |
| 717 | DependencyListHead::for_each(&*dependencies, |binding: &*const BindingHolder| { |
| 718 | let binding: &BindingHolder = &**binding; |
| 719 | let was_dirty: bool = binding.dirty.replace(val:true); |
| 720 | (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty); |
| 721 | |
| 722 | assert!( |
| 723 | !core::ptr::eq( |
| 724 | *(binding.dependencies.as_ptr() as *mut *const u32), |
| 725 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 726 | ), |
| 727 | "Const property marked as dirty" |
| 728 | ); |
| 729 | |
| 730 | if !was_dirty { |
| 731 | mark_dependencies_dirty(dependencies:binding.dependencies.as_ptr() as *mut DependencyListHead) |
| 732 | } |
| 733 | }); |
| 734 | } |
| 735 | |
| 736 | /// Types that can be set as bindings for a `Property<T>` |
| 737 | pub trait Binding<T> { |
| 738 | /// Evaluate the binding and return the new value |
| 739 | fn evaluate(&self, old_value: &T) -> T; |
| 740 | } |
| 741 | |
| 742 | impl<T, F: Fn() -> T> Binding<T> for F { |
| 743 | fn evaluate(&self, _value: &T) -> T { |
| 744 | self() |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | /// A Property that allow binding that track changes |
| 749 | /// |
| 750 | /// Property can have an assigned value, or binding. |
| 751 | /// When a binding is assigned, it is lazily evaluated on demand |
| 752 | /// when calling `get()`. |
| 753 | /// When accessing another property from a binding evaluation, |
| 754 | /// a dependency will be registered, such that when the property |
| 755 | /// change, the binding will automatically be updated |
| 756 | #[repr (C)] |
| 757 | pub struct Property<T> { |
| 758 | /// This is usually a pointer, but the least significant bit tells what it is |
| 759 | handle: PropertyHandle, |
| 760 | /// This is only safe to access when the lock flag is not set on the handle. |
| 761 | value: UnsafeCell<T>, |
| 762 | pinned: PhantomPinned, |
| 763 | /// Enabled only if compiled with `RUSTFLAGS='--cfg slint_debug_property'` |
| 764 | /// Note that adding this flag will also tell the rust compiler to set this |
| 765 | /// and that this will not work with C++ because of binary incompatibility |
| 766 | #[cfg (slint_debug_property)] |
| 767 | pub debug_name: RefCell<String>, |
| 768 | } |
| 769 | |
| 770 | impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> { |
| 771 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
| 772 | #[cfg (slint_debug_property)] |
| 773 | write!(f, "[{}]=" , self.debug_name.borrow())?; |
| 774 | write!( |
| 775 | f, |
| 776 | "Property( {:?}{})" , |
| 777 | self.get_internal(), |
| 778 | if self.is_dirty() { " (dirty)" } else { "" } |
| 779 | ) |
| 780 | } |
| 781 | } |
| 782 | |
| 783 | impl<T: Default> Default for Property<T> { |
| 784 | fn default() -> Self { |
| 785 | Self { |
| 786 | handle: Default::default(), |
| 787 | value: Default::default(), |
| 788 | pinned: PhantomPinned, |
| 789 | #[cfg (slint_debug_property)] |
| 790 | debug_name: Default::default(), |
| 791 | } |
| 792 | } |
| 793 | } |
| 794 | |
| 795 | impl<T: Clone> Property<T> { |
| 796 | /// Create a new property with this value |
| 797 | pub fn new(value: T) -> Self { |
| 798 | Self { |
| 799 | handle: Default::default(), |
| 800 | value: UnsafeCell::new(value), |
| 801 | pinned: PhantomPinned, |
| 802 | #[cfg (slint_debug_property)] |
| 803 | debug_name: Default::default(), |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | /// Same as [`Self::new`] but with a 'static string use for debugging only |
| 808 | pub fn new_named(value: T, _name: &'static str) -> Self { |
| 809 | Self { |
| 810 | handle: Default::default(), |
| 811 | value: UnsafeCell::new(value), |
| 812 | pinned: PhantomPinned, |
| 813 | #[cfg (slint_debug_property)] |
| 814 | debug_name: _name.to_owned().into(), |
| 815 | } |
| 816 | } |
| 817 | |
| 818 | /// Get the value of the property |
| 819 | /// |
| 820 | /// This may evaluate the binding if there is a binding and it is dirty |
| 821 | /// |
| 822 | /// If the function is called directly or indirectly from a binding evaluation |
| 823 | /// of another Property, a dependency will be registered. |
| 824 | /// |
| 825 | /// Panics if this property is get while evaluating its own binding or |
| 826 | /// cloning the value. |
| 827 | pub fn get(self: Pin<&Self>) -> T { |
| 828 | unsafe { self.handle.update(self.value.get()) }; |
| 829 | let handle = unsafe { Pin::new_unchecked(&self.handle) }; |
| 830 | handle.register_as_dependency_to_current_binding( |
| 831 | #[cfg (slint_debug_property)] |
| 832 | self.debug_name.borrow().as_str(), |
| 833 | ); |
| 834 | self.get_internal() |
| 835 | } |
| 836 | |
| 837 | /// Same as get() but without registering a dependency |
| 838 | /// |
| 839 | /// This allow to optimize bindings that know that they might not need to |
| 840 | /// re_evaluate themselves when the property change or that have registered |
| 841 | /// the dependency in another way. |
| 842 | /// |
| 843 | /// ## Example |
| 844 | /// ``` |
| 845 | /// use std::rc::Rc; |
| 846 | /// use i_slint_core::Property; |
| 847 | /// let prop1 = Rc::pin(Property::new(100)); |
| 848 | /// let prop2 = Rc::pin(Property::<i32>::default()); |
| 849 | /// prop2.as_ref().set_binding({ |
| 850 | /// let prop1 = prop1.clone(); // in order to move it into the closure. |
| 851 | /// move || { prop1.as_ref().get_untracked() + 30 } |
| 852 | /// }); |
| 853 | /// assert_eq!(prop2.as_ref().get(), 130); |
| 854 | /// prop1.set(200); |
| 855 | /// // changing prop1 do not affect the prop2 binding because no dependency was registered |
| 856 | /// assert_eq!(prop2.as_ref().get(), 130); |
| 857 | /// ``` |
| 858 | pub fn get_untracked(self: Pin<&Self>) -> T { |
| 859 | unsafe { self.handle.update(self.value.get()) }; |
| 860 | self.get_internal() |
| 861 | } |
| 862 | |
| 863 | /// Get the cached value without registering any dependencies or executing any binding |
| 864 | pub fn get_internal(&self) -> T { |
| 865 | self.handle.access(|_| { |
| 866 | // Safety: PropertyHandle::access ensure that the value is locked |
| 867 | unsafe { (*self.value.get()).clone() } |
| 868 | }) |
| 869 | } |
| 870 | |
| 871 | /// Change the value of this property |
| 872 | /// |
| 873 | /// If other properties have binding depending of this property, these properties will |
| 874 | /// be marked as dirty. |
| 875 | // FIXME pub fn set(self: Pin<&Self>, t: T) { |
| 876 | pub fn set(&self, t: T) |
| 877 | where |
| 878 | T: PartialEq, |
| 879 | { |
| 880 | let previous_binding_intercepted = self.handle.access(|b| { |
| 881 | b.is_some_and(|b| unsafe { |
| 882 | // Safety: b is a BindingHolder<T> |
| 883 | (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ()) |
| 884 | }) |
| 885 | }); |
| 886 | if !previous_binding_intercepted { |
| 887 | self.handle.remove_binding(); |
| 888 | } |
| 889 | |
| 890 | // Safety: PropertyHandle::access ensure that the value is locked |
| 891 | let has_value_changed = self.handle.access(|_| unsafe { |
| 892 | *self.value.get() != t && { |
| 893 | *self.value.get() = t; |
| 894 | true |
| 895 | } |
| 896 | }); |
| 897 | if has_value_changed { |
| 898 | self.handle.mark_dirty( |
| 899 | #[cfg (slint_debug_property)] |
| 900 | self.debug_name.borrow().as_str(), |
| 901 | ); |
| 902 | } |
| 903 | } |
| 904 | |
| 905 | /// Set a binding to this property. |
| 906 | /// |
| 907 | /// Bindings are evaluated lazily from calling get, and the return value of the binding |
| 908 | /// is the new value. |
| 909 | /// |
| 910 | /// If other properties have bindings depending of this property, these properties will |
| 911 | /// be marked as dirty. |
| 912 | /// |
| 913 | /// Closures of type `Fn()->T` implements `Binding<T>` and can be used as a binding |
| 914 | /// |
| 915 | /// ## Example |
| 916 | /// ``` |
| 917 | /// use std::rc::Rc; |
| 918 | /// use i_slint_core::Property; |
| 919 | /// let prop1 = Rc::pin(Property::new(100)); |
| 920 | /// let prop2 = Rc::pin(Property::<i32>::default()); |
| 921 | /// prop2.as_ref().set_binding({ |
| 922 | /// let prop1 = prop1.clone(); // in order to move it into the closure. |
| 923 | /// move || { prop1.as_ref().get() + 30 } |
| 924 | /// }); |
| 925 | /// assert_eq!(prop2.as_ref().get(), 130); |
| 926 | /// prop1.set(200); |
| 927 | /// // A change in prop1 forced the binding on prop2 to re_evaluate |
| 928 | /// assert_eq!(prop2.as_ref().get(), 230); |
| 929 | /// ``` |
| 930 | //FIXME pub fn set_binding(self: Pin<&Self>, f: impl Binding<T> + 'static) { |
| 931 | pub fn set_binding(&self, binding: impl Binding<T> + 'static) { |
| 932 | // Safety: This will make a binding callable for the type T |
| 933 | unsafe { |
| 934 | self.handle.set_binding( |
| 935 | move |val: *mut ()| { |
| 936 | let val = &mut *(val as *mut T); |
| 937 | *val = binding.evaluate(val); |
| 938 | BindingResult::KeepBinding |
| 939 | }, |
| 940 | #[cfg (slint_debug_property)] |
| 941 | self.debug_name.borrow().as_str(), |
| 942 | ) |
| 943 | } |
| 944 | self.handle.mark_dirty( |
| 945 | #[cfg (slint_debug_property)] |
| 946 | self.debug_name.borrow().as_str(), |
| 947 | ); |
| 948 | } |
| 949 | |
| 950 | /// Any of the properties accessed during the last evaluation of the closure called |
| 951 | /// from the last call to evaluate is potentially dirty. |
| 952 | pub fn is_dirty(&self) -> bool { |
| 953 | self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get())) |
| 954 | } |
| 955 | |
| 956 | /// Internal function to mark the property as dirty and notify dependencies, regardless of |
| 957 | /// whether the property value has actually changed or not. |
| 958 | pub fn mark_dirty(&self) { |
| 959 | self.handle.mark_dirty( |
| 960 | #[cfg (slint_debug_property)] |
| 961 | self.debug_name.borrow().as_str(), |
| 962 | ) |
| 963 | } |
| 964 | |
| 965 | /// Mark that this property will never be modified again and that no tracking should be done |
| 966 | pub fn set_constant(&self) { |
| 967 | self.handle.set_constant(); |
| 968 | } |
| 969 | } |
| 970 | |
| 971 | #[test ] |
| 972 | fn properties_simple_test() { |
| 973 | use pin_weak::rc::PinWeak; |
| 974 | use std::rc::Rc; |
| 975 | fn g(prop: &Property<i32>) -> i32 { |
| 976 | unsafe { Pin::new_unchecked(prop).get() } |
| 977 | } |
| 978 | |
| 979 | #[derive (Default)] |
| 980 | struct Component { |
| 981 | width: Property<i32>, |
| 982 | height: Property<i32>, |
| 983 | area: Property<i32>, |
| 984 | } |
| 985 | |
| 986 | let compo = Rc::pin(Component::default()); |
| 987 | let w = PinWeak::downgrade(compo.clone()); |
| 988 | compo.area.set_binding(move || { |
| 989 | let compo = w.upgrade().unwrap(); |
| 990 | g(&compo.width) * g(&compo.height) |
| 991 | }); |
| 992 | compo.width.set(4); |
| 993 | compo.height.set(8); |
| 994 | assert_eq!(g(&compo.width), 4); |
| 995 | assert_eq!(g(&compo.height), 8); |
| 996 | assert_eq!(g(&compo.area), 4 * 8); |
| 997 | |
| 998 | let w = PinWeak::downgrade(compo.clone()); |
| 999 | compo.width.set_binding(move || { |
| 1000 | let compo = w.upgrade().unwrap(); |
| 1001 | g(&compo.height) * 2 |
| 1002 | }); |
| 1003 | assert_eq!(g(&compo.width), 8 * 2); |
| 1004 | assert_eq!(g(&compo.height), 8); |
| 1005 | assert_eq!(g(&compo.area), 8 * 8 * 2); |
| 1006 | } |
| 1007 | |
| 1008 | impl<T: PartialEq + Clone + 'static> Property<T> { |
| 1009 | /// Link two property such that any change to one property is affecting the other property as if they |
| 1010 | /// where, in fact, a single property. |
| 1011 | /// The value or binding of prop2 is kept. |
| 1012 | pub fn link_two_way(prop1: Pin<&Self>, prop2: Pin<&Self>) { |
| 1013 | struct TwoWayBinding<T> { |
| 1014 | common_property: Pin<Rc<Property<T>>>, |
| 1015 | } |
| 1016 | unsafe impl<T: PartialEq + Clone + 'static> BindingCallable for TwoWayBinding<T> { |
| 1017 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
| 1018 | *(value as *mut T) = self.common_property.as_ref().get(); |
| 1019 | BindingResult::KeepBinding |
| 1020 | } |
| 1021 | |
| 1022 | unsafe fn intercept_set(self: Pin<&Self>, value: *const ()) -> bool { |
| 1023 | self.common_property.as_ref().set((*(value as *const T)).clone()); |
| 1024 | true |
| 1025 | } |
| 1026 | |
| 1027 | unsafe fn intercept_set_binding( |
| 1028 | self: Pin<&Self>, |
| 1029 | new_binding: *mut BindingHolder, |
| 1030 | ) -> bool { |
| 1031 | self.common_property.handle.set_binding_impl(new_binding); |
| 1032 | true |
| 1033 | } |
| 1034 | |
| 1035 | const IS_TWO_WAY_BINDING: bool = true; |
| 1036 | } |
| 1037 | |
| 1038 | #[cfg (slint_debug_property)] |
| 1039 | let debug_name = format!("<{}<=>{}>" , prop1.debug_name.borrow(), prop2.debug_name.borrow()); |
| 1040 | |
| 1041 | let value = prop2.get_internal(); |
| 1042 | |
| 1043 | let prop1_handle_val = prop1.handle.handle.get(); |
| 1044 | if prop1_handle_val & 0b10 == 0b10 { |
| 1045 | // Safety: the handle is a pointer to a binding |
| 1046 | let holder = unsafe { &*((prop1_handle_val & !0b11) as *const BindingHolder) }; |
| 1047 | if holder.is_two_way_binding { |
| 1048 | unsafe { |
| 1049 | // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T> |
| 1050 | let holder = |
| 1051 | &*((prop1_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>); |
| 1052 | // Safety: TwoWayBinding's T is the same as the type for both properties |
| 1053 | prop2.handle.set_binding( |
| 1054 | TwoWayBinding { common_property: holder.binding.common_property.clone() }, |
| 1055 | #[cfg (slint_debug_property)] |
| 1056 | debug_name.as_str(), |
| 1057 | ); |
| 1058 | } |
| 1059 | prop2.set(value); |
| 1060 | return; |
| 1061 | } |
| 1062 | }; |
| 1063 | |
| 1064 | let prop2_handle_val = prop2.handle.handle.get(); |
| 1065 | let handle = if prop2_handle_val & 0b10 == 0b10 { |
| 1066 | // Safety: the handle is a pointer to a binding |
| 1067 | let holder = unsafe { &*((prop2_handle_val & !0b11) as *const BindingHolder) }; |
| 1068 | if holder.is_two_way_binding { |
| 1069 | unsafe { |
| 1070 | // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T> |
| 1071 | let holder = |
| 1072 | &*((prop2_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>); |
| 1073 | // Safety: TwoWayBinding's T is the same as the type for both properties |
| 1074 | prop1.handle.set_binding( |
| 1075 | TwoWayBinding { common_property: holder.binding.common_property.clone() }, |
| 1076 | #[cfg (slint_debug_property)] |
| 1077 | debug_name.as_str(), |
| 1078 | ); |
| 1079 | } |
| 1080 | return; |
| 1081 | } |
| 1082 | // If prop2 is a binding, just "steal it" |
| 1083 | prop2.handle.handle.set(0); |
| 1084 | PropertyHandle { handle: Cell::new(prop2_handle_val) } |
| 1085 | } else { |
| 1086 | PropertyHandle::default() |
| 1087 | }; |
| 1088 | |
| 1089 | let common_property = Rc::pin(Property { |
| 1090 | handle, |
| 1091 | value: UnsafeCell::new(value), |
| 1092 | pinned: PhantomPinned, |
| 1093 | #[cfg (slint_debug_property)] |
| 1094 | debug_name: debug_name.clone().into(), |
| 1095 | }); |
| 1096 | // Safety: TwoWayBinding's T is the same as the type for both properties |
| 1097 | unsafe { |
| 1098 | prop1.handle.set_binding( |
| 1099 | TwoWayBinding { common_property: common_property.clone() }, |
| 1100 | #[cfg (slint_debug_property)] |
| 1101 | debug_name.as_str(), |
| 1102 | ); |
| 1103 | prop2.handle.set_binding( |
| 1104 | TwoWayBinding { common_property }, |
| 1105 | #[cfg (slint_debug_property)] |
| 1106 | debug_name.as_str(), |
| 1107 | ); |
| 1108 | } |
| 1109 | } |
| 1110 | } |
| 1111 | |
| 1112 | #[test ] |
| 1113 | fn property_two_ways_test() { |
| 1114 | let p1 = Rc::pin(Property::new(42)); |
| 1115 | let p2 = Rc::pin(Property::new(88)); |
| 1116 | |
| 1117 | let depends = Box::pin(Property::new(0)); |
| 1118 | depends.as_ref().set_binding({ |
| 1119 | let p1 = p1.clone(); |
| 1120 | move || p1.as_ref().get() + 8 |
| 1121 | }); |
| 1122 | assert_eq!(depends.as_ref().get(), 42 + 8); |
| 1123 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
| 1124 | assert_eq!(p1.as_ref().get(), 88); |
| 1125 | assert_eq!(p2.as_ref().get(), 88); |
| 1126 | assert_eq!(depends.as_ref().get(), 88 + 8); |
| 1127 | p2.as_ref().set(5); |
| 1128 | assert_eq!(p1.as_ref().get(), 5); |
| 1129 | assert_eq!(p2.as_ref().get(), 5); |
| 1130 | assert_eq!(depends.as_ref().get(), 5 + 8); |
| 1131 | p1.as_ref().set(22); |
| 1132 | assert_eq!(p1.as_ref().get(), 22); |
| 1133 | assert_eq!(p2.as_ref().get(), 22); |
| 1134 | assert_eq!(depends.as_ref().get(), 22 + 8); |
| 1135 | } |
| 1136 | |
| 1137 | #[test ] |
| 1138 | fn property_two_ways_test_binding() { |
| 1139 | let p1 = Rc::pin(Property::new(42)); |
| 1140 | let p2 = Rc::pin(Property::new(88)); |
| 1141 | let global = Rc::pin(Property::new(23)); |
| 1142 | p2.as_ref().set_binding({ |
| 1143 | let global = global.clone(); |
| 1144 | move || global.as_ref().get() + 9 |
| 1145 | }); |
| 1146 | |
| 1147 | let depends = Box::pin(Property::new(0)); |
| 1148 | depends.as_ref().set_binding({ |
| 1149 | let p1 = p1.clone(); |
| 1150 | move || p1.as_ref().get() + 8 |
| 1151 | }); |
| 1152 | |
| 1153 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
| 1154 | assert_eq!(p1.as_ref().get(), 23 + 9); |
| 1155 | assert_eq!(p2.as_ref().get(), 23 + 9); |
| 1156 | assert_eq!(depends.as_ref().get(), 23 + 9 + 8); |
| 1157 | global.as_ref().set(55); |
| 1158 | assert_eq!(p1.as_ref().get(), 55 + 9); |
| 1159 | assert_eq!(p2.as_ref().get(), 55 + 9); |
| 1160 | assert_eq!(depends.as_ref().get(), 55 + 9 + 8); |
| 1161 | } |
| 1162 | |
| 1163 | #[test ] |
| 1164 | fn property_two_ways_recurse_from_binding() { |
| 1165 | let xx = Rc::pin(Property::new(0)); |
| 1166 | |
| 1167 | let p1 = Rc::pin(Property::new(42)); |
| 1168 | let p2 = Rc::pin(Property::new(88)); |
| 1169 | let global = Rc::pin(Property::new(23)); |
| 1170 | |
| 1171 | let done = Rc::new(Cell::new(false)); |
| 1172 | xx.set_binding({ |
| 1173 | let p1 = p1.clone(); |
| 1174 | let p2 = p2.clone(); |
| 1175 | let global = global.clone(); |
| 1176 | let xx_weak = pin_weak::rc::PinWeak::downgrade(xx.clone()); |
| 1177 | move || { |
| 1178 | if !done.get() { |
| 1179 | done.set(true); |
| 1180 | Property::link_two_way(p1.as_ref(), p2.as_ref()); |
| 1181 | let xx = xx_weak.upgrade().unwrap(); |
| 1182 | p1.as_ref().set_binding(move || xx.as_ref().get() + 9); |
| 1183 | } |
| 1184 | global.as_ref().get() + 2 |
| 1185 | } |
| 1186 | }); |
| 1187 | assert_eq!(xx.as_ref().get(), 23 + 2); |
| 1188 | assert_eq!(p1.as_ref().get(), 23 + 2 + 9); |
| 1189 | assert_eq!(p2.as_ref().get(), 23 + 2 + 9); |
| 1190 | |
| 1191 | global.as_ref().set(55); |
| 1192 | assert_eq!(p1.as_ref().get(), 55 + 2 + 9); |
| 1193 | assert_eq!(p2.as_ref().get(), 55 + 2 + 9); |
| 1194 | assert_eq!(xx.as_ref().get(), 55 + 2); |
| 1195 | } |
| 1196 | |
| 1197 | #[test ] |
| 1198 | fn property_two_ways_binding_of_two_way_binding_first() { |
| 1199 | let p1_1 = Rc::pin(Property::new(2)); |
| 1200 | let p1_2 = Rc::pin(Property::new(4)); |
| 1201 | Property::link_two_way(p1_1.as_ref(), p1_2.as_ref()); |
| 1202 | |
| 1203 | assert_eq!(p1_1.as_ref().get(), 4); |
| 1204 | assert_eq!(p1_2.as_ref().get(), 4); |
| 1205 | |
| 1206 | let p2 = Rc::pin(Property::new(3)); |
| 1207 | Property::link_two_way(p1_1.as_ref(), p2.as_ref()); |
| 1208 | |
| 1209 | assert_eq!(p1_1.as_ref().get(), 3); |
| 1210 | assert_eq!(p1_2.as_ref().get(), 3); |
| 1211 | assert_eq!(p2.as_ref().get(), 3); |
| 1212 | |
| 1213 | p1_1.set(6); |
| 1214 | |
| 1215 | assert_eq!(p1_1.as_ref().get(), 6); |
| 1216 | assert_eq!(p1_2.as_ref().get(), 6); |
| 1217 | assert_eq!(p2.as_ref().get(), 6); |
| 1218 | |
| 1219 | p1_2.set(8); |
| 1220 | |
| 1221 | assert_eq!(p1_1.as_ref().get(), 8); |
| 1222 | assert_eq!(p1_2.as_ref().get(), 8); |
| 1223 | assert_eq!(p2.as_ref().get(), 8); |
| 1224 | |
| 1225 | p2.set(7); |
| 1226 | |
| 1227 | assert_eq!(p1_1.as_ref().get(), 7); |
| 1228 | assert_eq!(p1_2.as_ref().get(), 7); |
| 1229 | assert_eq!(p2.as_ref().get(), 7); |
| 1230 | } |
| 1231 | |
| 1232 | #[test ] |
| 1233 | fn property_two_ways_binding_of_two_way_binding_second() { |
| 1234 | let p1 = Rc::pin(Property::new(2)); |
| 1235 | let p2_1 = Rc::pin(Property::new(3)); |
| 1236 | let p2_2 = Rc::pin(Property::new(5)); |
| 1237 | Property::link_two_way(p2_1.as_ref(), p2_2.as_ref()); |
| 1238 | |
| 1239 | assert_eq!(p2_1.as_ref().get(), 5); |
| 1240 | assert_eq!(p2_2.as_ref().get(), 5); |
| 1241 | |
| 1242 | Property::link_two_way(p1.as_ref(), p2_2.as_ref()); |
| 1243 | |
| 1244 | assert_eq!(p1.as_ref().get(), 5); |
| 1245 | assert_eq!(p2_1.as_ref().get(), 5); |
| 1246 | assert_eq!(p2_2.as_ref().get(), 5); |
| 1247 | |
| 1248 | p1.set(6); |
| 1249 | |
| 1250 | assert_eq!(p1.as_ref().get(), 6); |
| 1251 | assert_eq!(p2_1.as_ref().get(), 6); |
| 1252 | assert_eq!(p2_2.as_ref().get(), 6); |
| 1253 | |
| 1254 | p2_1.set(7); |
| 1255 | |
| 1256 | assert_eq!(p1.as_ref().get(), 7); |
| 1257 | assert_eq!(p2_1.as_ref().get(), 7); |
| 1258 | assert_eq!(p2_2.as_ref().get(), 7); |
| 1259 | |
| 1260 | p2_2.set(9); |
| 1261 | |
| 1262 | assert_eq!(p1.as_ref().get(), 9); |
| 1263 | assert_eq!(p2_1.as_ref().get(), 9); |
| 1264 | assert_eq!(p2_2.as_ref().get(), 9); |
| 1265 | } |
| 1266 | |
| 1267 | #[test ] |
| 1268 | fn property_two_ways_binding_of_two_two_way_bindings() { |
| 1269 | let p1_1 = Rc::pin(Property::new(2)); |
| 1270 | let p1_2 = Rc::pin(Property::new(4)); |
| 1271 | Property::link_two_way(p1_1.as_ref(), p1_2.as_ref()); |
| 1272 | assert_eq!(p1_1.as_ref().get(), 4); |
| 1273 | assert_eq!(p1_2.as_ref().get(), 4); |
| 1274 | |
| 1275 | let p2_1 = Rc::pin(Property::new(3)); |
| 1276 | let p2_2 = Rc::pin(Property::new(5)); |
| 1277 | Property::link_two_way(p2_1.as_ref(), p2_2.as_ref()); |
| 1278 | |
| 1279 | assert_eq!(p2_1.as_ref().get(), 5); |
| 1280 | assert_eq!(p2_2.as_ref().get(), 5); |
| 1281 | |
| 1282 | Property::link_two_way(p1_1.as_ref(), p2_2.as_ref()); |
| 1283 | |
| 1284 | assert_eq!(p1_1.as_ref().get(), 5); |
| 1285 | assert_eq!(p1_2.as_ref().get(), 5); |
| 1286 | assert_eq!(p2_1.as_ref().get(), 5); |
| 1287 | assert_eq!(p2_2.as_ref().get(), 5); |
| 1288 | |
| 1289 | p1_1.set(6); |
| 1290 | assert_eq!(p1_1.as_ref().get(), 6); |
| 1291 | assert_eq!(p1_2.as_ref().get(), 6); |
| 1292 | assert_eq!(p2_1.as_ref().get(), 6); |
| 1293 | assert_eq!(p2_2.as_ref().get(), 6); |
| 1294 | |
| 1295 | p1_2.set(8); |
| 1296 | assert_eq!(p1_1.as_ref().get(), 8); |
| 1297 | assert_eq!(p1_2.as_ref().get(), 8); |
| 1298 | assert_eq!(p2_1.as_ref().get(), 8); |
| 1299 | assert_eq!(p2_2.as_ref().get(), 8); |
| 1300 | |
| 1301 | p2_1.set(7); |
| 1302 | assert_eq!(p1_1.as_ref().get(), 7); |
| 1303 | assert_eq!(p1_2.as_ref().get(), 7); |
| 1304 | assert_eq!(p2_1.as_ref().get(), 7); |
| 1305 | assert_eq!(p2_2.as_ref().get(), 7); |
| 1306 | |
| 1307 | p2_2.set(9); |
| 1308 | assert_eq!(p1_1.as_ref().get(), 9); |
| 1309 | assert_eq!(p1_2.as_ref().get(), 9); |
| 1310 | assert_eq!(p2_1.as_ref().get(), 9); |
| 1311 | assert_eq!(p2_2.as_ref().get(), 9); |
| 1312 | } |
| 1313 | |
| 1314 | mod change_tracker; |
| 1315 | pub use change_tracker::*; |
| 1316 | mod properties_animations; |
| 1317 | pub use crate::items::StateInfo; |
| 1318 | pub use properties_animations::*; |
| 1319 | |
| 1320 | struct StateInfoBinding<F> { |
| 1321 | dirty_time: Cell<Option<crate::animations::Instant>>, |
| 1322 | binding: F, |
| 1323 | } |
| 1324 | |
| 1325 | unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable for StateInfoBinding<F> { |
| 1326 | unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult { |
| 1327 | // Safety: We should only set this binding on a property of type StateInfo |
| 1328 | let value: &mut StateInfo = &mut *(value as *mut StateInfo); |
| 1329 | let new_state: i32 = (self.binding)(); |
| 1330 | let timestamp: Option = self.dirty_time.take(); |
| 1331 | if new_state != value.current_state { |
| 1332 | value.previous_state = value.current_state; |
| 1333 | value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick); |
| 1334 | value.current_state = new_state; |
| 1335 | } |
| 1336 | BindingResult::KeepBinding |
| 1337 | } |
| 1338 | |
| 1339 | fn mark_dirty(self: Pin<&Self>) { |
| 1340 | if self.dirty_time.get().is_none() { |
| 1341 | self.dirty_time.set(val:Some(crate::animations::current_tick())) |
| 1342 | } |
| 1343 | } |
| 1344 | } |
| 1345 | |
| 1346 | /// Sets a binding that returns a state to a StateInfo property |
| 1347 | pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) { |
| 1348 | let bind_callable: StateInfoBinding … + 'static> = StateInfoBinding { dirty_time: Cell::new(None), binding }; |
| 1349 | // Safety: The StateInfoBinding is a BindingCallable for type StateInfo |
| 1350 | unsafe { |
| 1351 | property.handle.set_binding( |
| 1352 | bind_callable, |
| 1353 | #[cfg (slint_debug_property)] |
| 1354 | property.debug_name.borrow().as_str(), |
| 1355 | ) |
| 1356 | } |
| 1357 | } |
| 1358 | |
| 1359 | #[doc (hidden)] |
| 1360 | pub trait PropertyDirtyHandler { |
| 1361 | fn notify(self: Pin<&Self>); |
| 1362 | } |
| 1363 | |
| 1364 | impl PropertyDirtyHandler for () { |
| 1365 | fn notify(self: Pin<&Self>) {} |
| 1366 | } |
| 1367 | |
| 1368 | impl<F: Fn()> PropertyDirtyHandler for F { |
| 1369 | fn notify(self: Pin<&Self>) { |
| 1370 | (self.get_ref())() |
| 1371 | } |
| 1372 | } |
| 1373 | |
| 1374 | /// This structure allow to run a closure that queries properties, and can report |
| 1375 | /// if any property we accessed have become dirty |
| 1376 | pub struct PropertyTracker<DirtyHandler = ()> { |
| 1377 | holder: BindingHolder<DirtyHandler>, |
| 1378 | } |
| 1379 | |
| 1380 | impl Default for PropertyTracker<()> { |
| 1381 | fn default() -> Self { |
| 1382 | static VT: &BindingVTable = &BindingVTable { |
| 1383 | drop: |_| (), |
| 1384 | evaluate: |_, _| BindingResult::KeepBinding, |
| 1385 | mark_dirty: |_, _| (), |
| 1386 | intercept_set: |_, _| false, |
| 1387 | intercept_set_binding: |_, _| false, |
| 1388 | }; |
| 1389 | |
| 1390 | let holder: BindingHolder = BindingHolder { |
| 1391 | dependencies: Cell::new(0), |
| 1392 | dep_nodes: Default::default(), |
| 1393 | vtable: VT, |
| 1394 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
| 1395 | is_two_way_binding: false, |
| 1396 | pinned: PhantomPinned, |
| 1397 | binding: (), |
| 1398 | #[cfg (slint_debug_property)] |
| 1399 | debug_name: "<PropertyTracker<()>>" .into(), |
| 1400 | }; |
| 1401 | Self { holder } |
| 1402 | } |
| 1403 | } |
| 1404 | |
| 1405 | impl<DirtyHandler> Drop for PropertyTracker<DirtyHandler> { |
| 1406 | fn drop(&mut self) { |
| 1407 | unsafe { |
| 1408 | DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead); |
| 1409 | } |
| 1410 | } |
| 1411 | } |
| 1412 | |
| 1413 | impl<DirtyHandler: PropertyDirtyHandler> PropertyTracker<DirtyHandler> { |
| 1414 | #[cfg (slint_debug_property)] |
| 1415 | /// set the debug name when `cfg(slint_debug_property` |
| 1416 | pub fn set_debug_name(&mut self, debug_name: String) { |
| 1417 | self.holder.debug_name = debug_name; |
| 1418 | } |
| 1419 | |
| 1420 | /// Register this property tracker as a dependency to the current binding/property tracker being evaluated |
| 1421 | pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) { |
| 1422 | if CURRENT_BINDING.is_set() { |
| 1423 | CURRENT_BINDING.with(|cur_binding| { |
| 1424 | if let Some(cur_binding) = cur_binding { |
| 1425 | debug_assert!(!core::ptr::eq( |
| 1426 | self.holder.dependencies.get() as *const u32, |
| 1427 | (&CONSTANT_PROPERTY_SENTINEL) as *const u32, |
| 1428 | )); |
| 1429 | cur_binding.register_self_as_dependency( |
| 1430 | self.holder.dependencies.as_ptr() as *mut DependencyListHead, |
| 1431 | #[cfg (slint_debug_property)] |
| 1432 | &self.holder.debug_name, |
| 1433 | ); |
| 1434 | } |
| 1435 | }); |
| 1436 | } |
| 1437 | } |
| 1438 | |
| 1439 | /// Any of the properties accessed during the last evaluation of the closure called |
| 1440 | /// from the last call to evaluate is potentially dirty. |
| 1441 | pub fn is_dirty(&self) -> bool { |
| 1442 | self.holder.dirty.get() |
| 1443 | } |
| 1444 | |
| 1445 | /// Evaluate the function, and record dependencies of properties accessed within this function. |
| 1446 | /// If this is called during the evaluation of another property binding or property tracker, then |
| 1447 | /// any changes to accessed properties will also mark the other binding/tracker dirty. |
| 1448 | pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { |
| 1449 | self.register_as_dependency_to_current_binding(); |
| 1450 | self.evaluate_as_dependency_root(f) |
| 1451 | } |
| 1452 | |
| 1453 | /// Evaluate the function, and record dependencies of properties accessed within this function. |
| 1454 | /// If this is called during the evaluation of another property binding or property tracker, then |
| 1455 | /// any changes to accessed properties will not propagate to the other tracker. |
| 1456 | pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R { |
| 1457 | // clear all the nodes so that we can start from scratch |
| 1458 | self.holder.dep_nodes.set(Default::default()); |
| 1459 | |
| 1460 | // Safety: it is safe to project the holder as we don't implement drop or unpin |
| 1461 | let pinned_holder = unsafe { |
| 1462 | self.map_unchecked(|s| { |
| 1463 | core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder) |
| 1464 | }) |
| 1465 | }; |
| 1466 | let r = CURRENT_BINDING.set(Some(pinned_holder), f); |
| 1467 | self.holder.dirty.set(false); |
| 1468 | r |
| 1469 | } |
| 1470 | |
| 1471 | /// Call [`Self::evaluate`] if and only if it is dirty. |
| 1472 | /// But register a dependency in any case. |
| 1473 | pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> { |
| 1474 | self.register_as_dependency_to_current_binding(); |
| 1475 | self.is_dirty().then(|| self.evaluate_as_dependency_root(f)) |
| 1476 | } |
| 1477 | |
| 1478 | /// Mark this PropertyTracker as dirty |
| 1479 | pub fn set_dirty(&self) { |
| 1480 | self.holder.dirty.set(true); |
| 1481 | unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) }; |
| 1482 | } |
| 1483 | |
| 1484 | /// Sets the specified callback handler function, which will be called if any |
| 1485 | /// properties that this tracker depends on becomes dirty. |
| 1486 | /// |
| 1487 | /// The `handler` `PropertyDirtyHandler` is a trait which is implemented for |
| 1488 | /// any `Fn()` closure |
| 1489 | /// |
| 1490 | /// Note that the handler will be invoked immediately when a property is modified or |
| 1491 | /// marked as dirty. In particular, the involved property are still in a locked |
| 1492 | /// state and should not be accessed while the handler is run. This function can be |
| 1493 | /// useful to mark some work to be done later. |
| 1494 | pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self { |
| 1495 | /// Safety: _self must be a pointer to a `BindingHolder<DirtyHandler>` |
| 1496 | unsafe fn mark_dirty<B: PropertyDirtyHandler>( |
| 1497 | _self: *const BindingHolder, |
| 1498 | was_dirty: bool, |
| 1499 | ) { |
| 1500 | if !was_dirty { |
| 1501 | Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify(); |
| 1502 | } |
| 1503 | } |
| 1504 | |
| 1505 | trait HasBindingVTable { |
| 1506 | const VT: &'static BindingVTable; |
| 1507 | } |
| 1508 | impl<B: PropertyDirtyHandler> HasBindingVTable for B { |
| 1509 | const VT: &'static BindingVTable = &BindingVTable { |
| 1510 | drop: |_| (), |
| 1511 | evaluate: |_, _| BindingResult::KeepBinding, |
| 1512 | mark_dirty: mark_dirty::<B>, |
| 1513 | intercept_set: |_, _| false, |
| 1514 | intercept_set_binding: |_, _| false, |
| 1515 | }; |
| 1516 | } |
| 1517 | |
| 1518 | let holder = BindingHolder { |
| 1519 | dependencies: Cell::new(0), |
| 1520 | dep_nodes: Default::default(), |
| 1521 | vtable: <DirtyHandler as HasBindingVTable>::VT, |
| 1522 | dirty: Cell::new(true), // starts dirty so it evaluates the property when used |
| 1523 | is_two_way_binding: false, |
| 1524 | pinned: PhantomPinned, |
| 1525 | binding: handler, |
| 1526 | #[cfg (slint_debug_property)] |
| 1527 | debug_name: "<PropertyTracker>" .into(), |
| 1528 | }; |
| 1529 | Self { holder } |
| 1530 | } |
| 1531 | } |
| 1532 | |
| 1533 | #[test ] |
| 1534 | fn test_property_listener_scope() { |
| 1535 | let scope = Box::pin(PropertyTracker::default()); |
| 1536 | let prop1 = Box::pin(Property::new(42)); |
| 1537 | assert!(scope.is_dirty()); // It is dirty at the beginning |
| 1538 | |
| 1539 | let r = scope.as_ref().evaluate(|| prop1.as_ref().get()); |
| 1540 | assert_eq!(r, 42); |
| 1541 | assert!(!scope.is_dirty()); // It is no longer dirty |
| 1542 | prop1.as_ref().set(88); |
| 1543 | assert!(scope.is_dirty()); // now dirty for prop1 changed. |
| 1544 | let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1); |
| 1545 | assert_eq!(r, 89); |
| 1546 | assert!(!scope.is_dirty()); |
| 1547 | let r = scope.as_ref().evaluate(|| 12); |
| 1548 | assert_eq!(r, 12); |
| 1549 | assert!(!scope.is_dirty()); |
| 1550 | prop1.as_ref().set(1); |
| 1551 | assert!(!scope.is_dirty()); |
| 1552 | scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty" )); |
| 1553 | scope.set_dirty(); |
| 1554 | let mut ok = false; |
| 1555 | scope.as_ref().evaluate_if_dirty(|| ok = true); |
| 1556 | assert!(ok); |
| 1557 | } |
| 1558 | |
| 1559 | #[test ] |
| 1560 | fn test_nested_property_trackers() { |
| 1561 | let tracker1 = Box::pin(PropertyTracker::default()); |
| 1562 | let tracker2 = Box::pin(PropertyTracker::default()); |
| 1563 | let prop = Box::pin(Property::new(42)); |
| 1564 | |
| 1565 | let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get())); |
| 1566 | assert_eq!(r, 42); |
| 1567 | |
| 1568 | prop.as_ref().set(1); |
| 1569 | assert!(tracker2.as_ref().is_dirty()); |
| 1570 | assert!(tracker1.as_ref().is_dirty()); |
| 1571 | |
| 1572 | let r = tracker1 |
| 1573 | .as_ref() |
| 1574 | .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get())); |
| 1575 | assert_eq!(r, 1); |
| 1576 | prop.as_ref().set(100); |
| 1577 | assert!(tracker2.as_ref().is_dirty()); |
| 1578 | assert!(!tracker1.as_ref().is_dirty()); |
| 1579 | } |
| 1580 | |
| 1581 | #[test ] |
| 1582 | fn test_property_dirty_handler() { |
| 1583 | let call_flag = Rc::new(Cell::new(false)); |
| 1584 | let tracker = Box::pin(PropertyTracker::new_with_dirty_handler({ |
| 1585 | let call_flag = call_flag.clone(); |
| 1586 | move || { |
| 1587 | (*call_flag).set(true); |
| 1588 | } |
| 1589 | })); |
| 1590 | let prop = Box::pin(Property::new(42)); |
| 1591 | |
| 1592 | let r = tracker.as_ref().evaluate(|| prop.as_ref().get()); |
| 1593 | |
| 1594 | assert_eq!(r, 42); |
| 1595 | assert!(!tracker.as_ref().is_dirty()); |
| 1596 | assert!(!call_flag.get()); |
| 1597 | |
| 1598 | prop.as_ref().set(100); |
| 1599 | assert!(tracker.as_ref().is_dirty()); |
| 1600 | assert!(call_flag.get()); |
| 1601 | |
| 1602 | // Repeated changes before evaluation should not trigger further |
| 1603 | // change handler calls, otherwise it would be a notification storm. |
| 1604 | call_flag.set(false); |
| 1605 | prop.as_ref().set(101); |
| 1606 | assert!(tracker.as_ref().is_dirty()); |
| 1607 | assert!(!call_flag.get()); |
| 1608 | } |
| 1609 | |
| 1610 | #[test ] |
| 1611 | fn test_property_tracker_drop() { |
| 1612 | let outer_tracker = Box::pin(PropertyTracker::default()); |
| 1613 | let inner_tracker = Box::pin(PropertyTracker::default()); |
| 1614 | let prop = Box::pin(Property::new(42)); |
| 1615 | |
| 1616 | let r = |
| 1617 | outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); |
| 1618 | assert_eq!(r, 42); |
| 1619 | |
| 1620 | drop(inner_tracker); |
| 1621 | prop.as_ref().set(200); // don't crash |
| 1622 | } |
| 1623 | |
| 1624 | #[test ] |
| 1625 | fn test_nested_property_tracker_dirty() { |
| 1626 | let outer_tracker = Box::pin(PropertyTracker::default()); |
| 1627 | let inner_tracker = Box::pin(PropertyTracker::default()); |
| 1628 | let prop = Box::pin(Property::new(42)); |
| 1629 | |
| 1630 | let r = |
| 1631 | outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get())); |
| 1632 | assert_eq!(r, 42); |
| 1633 | |
| 1634 | assert!(!outer_tracker.is_dirty()); |
| 1635 | assert!(!inner_tracker.is_dirty()); |
| 1636 | |
| 1637 | // Let's pretend that there was another dependency unaccounted first, mark the inner tracker as dirty |
| 1638 | // by hand. |
| 1639 | inner_tracker.as_ref().set_dirty(); |
| 1640 | assert!(outer_tracker.is_dirty()); |
| 1641 | } |
| 1642 | |
| 1643 | #[test ] |
| 1644 | #[allow (clippy::redundant_closure)] |
| 1645 | fn test_nested_property_tracker_evaluate_if_dirty() { |
| 1646 | let outer_tracker = Box::pin(PropertyTracker::default()); |
| 1647 | let inner_tracker = Box::pin(PropertyTracker::default()); |
| 1648 | let prop = Box::pin(Property::new(42)); |
| 1649 | |
| 1650 | let mut cache = 0; |
| 1651 | let mut cache_or_evaluate = || { |
| 1652 | if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) { |
| 1653 | cache = x; |
| 1654 | } |
| 1655 | cache |
| 1656 | }; |
| 1657 | let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); |
| 1658 | assert_eq!(r, 43); |
| 1659 | assert!(!outer_tracker.is_dirty()); |
| 1660 | assert!(!inner_tracker.is_dirty()); |
| 1661 | prop.as_ref().set(11); |
| 1662 | assert!(outer_tracker.is_dirty()); |
| 1663 | assert!(inner_tracker.is_dirty()); |
| 1664 | let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate()); |
| 1665 | assert_eq!(r, 12); |
| 1666 | } |
| 1667 | |
| 1668 | #[cfg (feature = "ffi" )] |
| 1669 | pub(crate) mod ffi; |
| 1670 | |