Skip to main content

i_slint_core/
properties.rs

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