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