i_slint_core/
properties.rs

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