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