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