Skip to main content

i_slint_core/
properties.rs

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