1#![allow(unsafe_code)]
14#![warn(missing_docs)]
15
16mod 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            while let Some(mut x) = core::mem::take(&mut self.0) {
39                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            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        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    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        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        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        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    pub struct DependencyNode<T> {
208        next: Cell<*const DependencyNode<T>>,
209        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        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                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
273static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
276
277#[derive(Copy, Clone, Debug, Eq, PartialEq)]
279enum BindingResult {
280    KeepBinding,
282    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
296unsafe trait BindingCallable {
302    unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult;
305
306    fn mark_dirty(self: Pin<&Self>) {}
309
310    unsafe fn intercept_set(self: Pin<&Self>, _value: *const ()) -> bool {
316        false
317    }
318
319    unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
323        false
324    }
325
326    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    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
377pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
380    CURRENT_BINDING.set(None, f)
381}
382
383pub fn is_currently_tracking() -> bool {
386    CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x| x.is_some())
387}
388
389#[repr(C)]
391struct BindingHolder<B = ()> {
392    dependencies: Cell<usize>,
394    dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
397    vtable: &'static BindingVTable,
398    dirty: Cell<bool>,
400    is_two_way_binding: bool,
402    pinned: PhantomPinned,
403    #[cfg(slint_debug_property)]
404    pub debug_name: alloc::string::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    unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
426        drop(Box::from_raw(_self as *mut BindingHolder<B>));
427    }
428
429    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    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    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), 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    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    fn lock_flag(&self) -> bool {
517        self.handle.get() & 0b1 == 1
518    }
519    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    fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
528        #[cfg(slint_debug_property)]
529        if self.lock_flag() {
530            unsafe {
531                let handle = self.handle.get();
532                if handle & 0b10 == 0b10 {
533                    let binding = &mut *((handle & !0b11) as *mut BindingHolder);
534                    let debug_name = &binding.debug_name;
535                    panic!("Recursion detected with property {debug_name}");
536                }
537            }
538        }
539        assert!(!self.lock_flag(), "Recursion detected");
540        unsafe {
541            self.set_lock_flag(true);
542            scopeguard::defer! { self.set_lock_flag(false); }
543            let handle = self.handle.get();
544            let binding = if handle & 0b10 == 0b10 {
545                Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder)))
546            } else {
547                None
548            };
549            f(binding)
550        }
551    }
552
553    fn remove_binding(&self) {
554        assert!(!self.lock_flag(), "Recursion detected");
555        let val = self.handle.get();
556        if val & 0b10 == 0b10 {
557            unsafe {
558                self.set_lock_flag(true);
559                let binding = (val & !0b11) as *mut BindingHolder;
560                let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
561                if (*binding).dependencies.get() == const_sentinel {
562                    self.handle.set(const_sentinel);
563                    (*binding).dependencies.set(0);
564                } else {
565                    DependencyListHead::mem_move(
566                        (*binding).dependencies.as_ptr() as *mut DependencyListHead,
567                        self.handle.as_ptr() as *mut DependencyListHead,
568                    );
569                }
570                ((*binding).vtable.drop)(binding);
571            }
572            debug_assert!(self.handle.get() & 0b11 == 0);
573        }
574    }
575
576    unsafe fn set_binding<B: BindingCallable + 'static>(
578        &self,
579        binding: B,
580        #[cfg(slint_debug_property)] debug_name: &str,
581    ) {
582        let binding = alloc_binding_holder::<B>(binding);
583        #[cfg(slint_debug_property)]
584        {
585            (*binding).debug_name = debug_name.into();
586        }
587        self.set_binding_impl(binding);
588    }
589
590    fn set_binding_impl(&self, binding: *mut BindingHolder) {
592        let previous_binding_intercepted = self.access(|b| {
593            b.is_some_and(|b| unsafe {
594                (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
596            })
597        });
598
599        if previous_binding_intercepted {
600            return;
601        }
602
603        self.remove_binding();
604        debug_assert!((binding as usize) & 0b11 == 0);
605        debug_assert!(self.handle.get() & 0b11 == 0);
606        let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
607        let is_constant = self.handle.get() == const_sentinel;
608        unsafe {
609            if is_constant {
610                (*binding).dependencies.set(const_sentinel);
611            } else {
612                DependencyListHead::mem_move(
613                    self.handle.as_ptr() as *mut DependencyListHead,
614                    (*binding).dependencies.as_ptr() as *mut DependencyListHead,
615                );
616            }
617        }
618        self.handle.set((binding as usize) | 0b10);
619        if !is_constant {
620            self.mark_dirty(
621                #[cfg(slint_debug_property)]
622                "",
623            );
624        }
625    }
626
627    fn dependencies(&self) -> *mut DependencyListHead {
628        assert!(!self.lock_flag(), "Recursion detected");
629        if (self.handle.get() & 0b10) != 0 {
630            self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
631        } else {
632            self.handle.as_ptr() as *mut DependencyListHead
633        }
634    }
635
636    unsafe fn update<T>(&self, value: *mut T) {
639        let remove = self.access(|binding| {
640            if let Some(mut binding) = binding {
641                if binding.dirty.get() {
642                    binding.dep_nodes.set(Default::default());
644                    let r = (binding.vtable.evaluate)(
645                        binding.as_mut().get_unchecked_mut() as *mut BindingHolder,
646                        value as *mut (),
647                    );
648                    binding.dirty.set(false);
649                    if r == BindingResult::RemoveBinding {
650                        return true;
651                    }
652                }
653            }
654            false
655        });
656        if remove {
657            self.remove_binding()
658        }
659    }
660
661    fn register_as_dependency_to_current_binding(
663        self: Pin<&Self>,
664        #[cfg(slint_debug_property)] debug_name: &str,
665    ) {
666        if CURRENT_BINDING.is_set() {
667            CURRENT_BINDING.with(|cur_binding| {
668                if let Some(cur_binding) = cur_binding {
669                    let dependencies = self.dependencies();
670                    if !core::ptr::eq(
671                        unsafe { *(dependencies as *mut *const u32) },
672                        (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
673                    ) {
674                        cur_binding.register_self_as_dependency(
675                            dependencies,
676                            #[cfg(slint_debug_property)]
677                            debug_name,
678                        );
679                    }
680                }
681            });
682        }
683    }
684
685    fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
686        #[cfg(not(slint_debug_property))]
687        let debug_name = "";
688        unsafe {
689            let dependencies = self.dependencies();
690            assert!(
691                !core::ptr::eq(
692                    *(dependencies as *mut *const u32),
693                    (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
694                ),
695                "Constant property being changed {debug_name}"
696            );
697            mark_dependencies_dirty(dependencies)
698        };
699    }
700
701    fn set_constant(&self) {
702        unsafe {
703            let dependencies = self.dependencies();
704            if !core::ptr::eq(
705                *(dependencies as *mut *const u32),
706                (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
707            ) {
708                DependencyListHead::drop(dependencies);
709                *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32
710            }
711        }
712    }
713}
714
715impl Drop for PropertyHandle {
716    fn drop(&mut self) {
717        self.remove_binding();
718        debug_assert!(self.handle.get() & 0b11 == 0);
719        if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 {
720            unsafe {
721                DependencyListHead::drop(self.handle.as_ptr() as *mut _);
722            }
723        }
724    }
725}
726
727unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
729    debug_assert!(!core::ptr::eq(
730        *(dependencies as *mut *const u32),
731        (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
732    ));
733    DependencyListHead::for_each(&*dependencies, |binding| {
734        let binding: &BindingHolder = &**binding;
735        let was_dirty = binding.dirty.replace(true);
736        (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
737
738        assert!(
739            !core::ptr::eq(
740                *(binding.dependencies.as_ptr() as *mut *const u32),
741                (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
742            ),
743            "Const property marked as dirty"
744        );
745
746        if !was_dirty {
747            mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead)
748        }
749    });
750}
751
752pub trait Binding<T> {
754    fn evaluate(&self, old_value: &T) -> T;
756}
757
758impl<T, F: Fn() -> T> Binding<T> for F {
759    fn evaluate(&self, _value: &T) -> T {
760        self()
761    }
762}
763
764#[repr(C)]
773pub struct Property<T> {
774    handle: PropertyHandle,
776    value: UnsafeCell<T>,
778    pinned: PhantomPinned,
779    #[cfg(slint_debug_property)]
783    pub debug_name: RefCell<alloc::string::String>,
784}
785
786impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
787    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
788        #[cfg(slint_debug_property)]
789        write!(f, "[{}]=", self.debug_name.borrow())?;
790        write!(
791            f,
792            "Property({:?}{})",
793            self.get_internal(),
794            if self.is_dirty() { " (dirty)" } else { "" }
795        )
796    }
797}
798
799impl<T: Default> Default for Property<T> {
800    fn default() -> Self {
801        Self {
802            handle: Default::default(),
803            value: Default::default(),
804            pinned: PhantomPinned,
805            #[cfg(slint_debug_property)]
806            debug_name: Default::default(),
807        }
808    }
809}
810
811impl<T: Clone> Property<T> {
812    pub fn new(value: T) -> Self {
814        Self {
815            handle: Default::default(),
816            value: UnsafeCell::new(value),
817            pinned: PhantomPinned,
818            #[cfg(slint_debug_property)]
819            debug_name: Default::default(),
820        }
821    }
822
823    pub fn new_named(value: T, _name: &'static str) -> Self {
825        Self {
826            handle: Default::default(),
827            value: UnsafeCell::new(value),
828            pinned: PhantomPinned,
829            #[cfg(slint_debug_property)]
830            debug_name: RefCell::new(_name.into()),
831        }
832    }
833
834    pub fn get(self: Pin<&Self>) -> T {
844        unsafe { self.handle.update(self.value.get()) };
845        let handle = unsafe { Pin::new_unchecked(&self.handle) };
846        handle.register_as_dependency_to_current_binding(
847            #[cfg(slint_debug_property)]
848            self.debug_name.borrow().as_str(),
849        );
850        self.get_internal()
851    }
852
853    pub fn get_untracked(self: Pin<&Self>) -> T {
875        unsafe { self.handle.update(self.value.get()) };
876        self.get_internal()
877    }
878
879    pub fn get_internal(&self) -> T {
881        self.handle.access(|_| {
882            unsafe { (*self.value.get()).clone() }
884        })
885    }
886
887    pub fn set(&self, t: T)
893    where
894        T: PartialEq,
895    {
896        let previous_binding_intercepted = self.handle.access(|b| {
897            b.is_some_and(|b| unsafe {
898                (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ())
900            })
901        });
902        if !previous_binding_intercepted {
903            self.handle.remove_binding();
904        }
905
906        let has_value_changed = self.handle.access(|_| unsafe {
908            *self.value.get() != t && {
909                *self.value.get() = t;
910                true
911            }
912        });
913        if has_value_changed {
914            self.handle.mark_dirty(
915                #[cfg(slint_debug_property)]
916                self.debug_name.borrow().as_str(),
917            );
918        }
919    }
920
921    pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
948        unsafe {
950            self.handle.set_binding(
951                move |val: *mut ()| {
952                    let val = &mut *(val as *mut T);
953                    *val = binding.evaluate(val);
954                    BindingResult::KeepBinding
955                },
956                #[cfg(slint_debug_property)]
957                self.debug_name.borrow().as_str(),
958            )
959        }
960        self.handle.mark_dirty(
961            #[cfg(slint_debug_property)]
962            self.debug_name.borrow().as_str(),
963        );
964    }
965
966    pub fn is_dirty(&self) -> bool {
969        self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
970    }
971
972    pub fn mark_dirty(&self) {
975        self.handle.mark_dirty(
976            #[cfg(slint_debug_property)]
977            self.debug_name.borrow().as_str(),
978        )
979    }
980
981    pub fn set_constant(&self) {
983        self.handle.set_constant();
984    }
985}
986
987#[test]
988fn properties_simple_test() {
989    use pin_weak::rc::PinWeak;
990    use std::rc::Rc;
991    fn g(prop: &Property<i32>) -> i32 {
992        unsafe { Pin::new_unchecked(prop).get() }
993    }
994
995    #[derive(Default)]
996    struct Component {
997        width: Property<i32>,
998        height: Property<i32>,
999        area: Property<i32>,
1000    }
1001
1002    let compo = Rc::pin(Component::default());
1003    let w = PinWeak::downgrade(compo.clone());
1004    compo.area.set_binding(move || {
1005        let compo = w.upgrade().unwrap();
1006        g(&compo.width) * g(&compo.height)
1007    });
1008    compo.width.set(4);
1009    compo.height.set(8);
1010    assert_eq!(g(&compo.width), 4);
1011    assert_eq!(g(&compo.height), 8);
1012    assert_eq!(g(&compo.area), 4 * 8);
1013
1014    let w = PinWeak::downgrade(compo.clone());
1015    compo.width.set_binding(move || {
1016        let compo = w.upgrade().unwrap();
1017        g(&compo.height) * 2
1018    });
1019    assert_eq!(g(&compo.width), 8 * 2);
1020    assert_eq!(g(&compo.height), 8);
1021    assert_eq!(g(&compo.area), 8 * 8 * 2);
1022}
1023
1024impl<T: PartialEq + Clone + 'static> Property<T> {
1025    pub fn link_two_way(prop1: Pin<&Self>, prop2: Pin<&Self>) {
1029        struct TwoWayBinding<T> {
1030            common_property: Pin<Rc<Property<T>>>,
1031        }
1032        unsafe impl<T: PartialEq + Clone + 'static> BindingCallable for TwoWayBinding<T> {
1033            unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
1034                *(value as *mut T) = self.common_property.as_ref().get();
1035                BindingResult::KeepBinding
1036            }
1037
1038            unsafe fn intercept_set(self: Pin<&Self>, value: *const ()) -> bool {
1039                self.common_property.as_ref().set((*(value as *const T)).clone());
1040                true
1041            }
1042
1043            unsafe fn intercept_set_binding(
1044                self: Pin<&Self>,
1045                new_binding: *mut BindingHolder,
1046            ) -> bool {
1047                self.common_property.handle.set_binding_impl(new_binding);
1048                true
1049            }
1050
1051            const IS_TWO_WAY_BINDING: bool = true;
1052        }
1053
1054        #[cfg(slint_debug_property)]
1055        let debug_name =
1056            alloc::format!("<{}<=>{}>", prop1.debug_name.borrow(), prop2.debug_name.borrow());
1057
1058        let value = prop2.get_internal();
1059
1060        let prop1_handle_val = prop1.handle.handle.get();
1061        if prop1_handle_val & 0b10 == 0b10 {
1062            let holder = unsafe { &*((prop1_handle_val & !0b11) as *const BindingHolder) };
1064            if holder.is_two_way_binding {
1065                unsafe {
1066                    let holder =
1068                        &*((prop1_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>);
1069                    prop2.handle.set_binding(
1071                        TwoWayBinding { common_property: holder.binding.common_property.clone() },
1072                        #[cfg(slint_debug_property)]
1073                        debug_name.as_str(),
1074                    );
1075                }
1076                prop2.set(value);
1077                return;
1078            }
1079        };
1080
1081        let prop2_handle_val = prop2.handle.handle.get();
1082        let handle = if prop2_handle_val & 0b10 == 0b10 {
1083            let holder = unsafe { &*((prop2_handle_val & !0b11) as *const BindingHolder) };
1085            if holder.is_two_way_binding {
1086                unsafe {
1087                    let holder =
1089                        &*((prop2_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>);
1090                    prop1.handle.set_binding(
1092                        TwoWayBinding { common_property: holder.binding.common_property.clone() },
1093                        #[cfg(slint_debug_property)]
1094                        debug_name.as_str(),
1095                    );
1096                }
1097                return;
1098            }
1099            prop2.handle.handle.set(0);
1101            PropertyHandle { handle: Cell::new(prop2_handle_val) }
1102        } else {
1103            PropertyHandle::default()
1104        };
1105
1106        let common_property = Rc::pin(Property {
1107            handle,
1108            value: UnsafeCell::new(value),
1109            pinned: PhantomPinned,
1110            #[cfg(slint_debug_property)]
1111            debug_name: debug_name.clone().into(),
1112        });
1113        unsafe {
1115            prop1.handle.set_binding(
1116                TwoWayBinding { common_property: common_property.clone() },
1117                #[cfg(slint_debug_property)]
1118                debug_name.as_str(),
1119            );
1120            prop2.handle.set_binding(
1121                TwoWayBinding { common_property },
1122                #[cfg(slint_debug_property)]
1123                debug_name.as_str(),
1124            );
1125        }
1126    }
1127}
1128
1129#[test]
1130fn property_two_ways_test() {
1131    let p1 = Rc::pin(Property::new(42));
1132    let p2 = Rc::pin(Property::new(88));
1133
1134    let depends = Box::pin(Property::new(0));
1135    depends.as_ref().set_binding({
1136        let p1 = p1.clone();
1137        move || p1.as_ref().get() + 8
1138    });
1139    assert_eq!(depends.as_ref().get(), 42 + 8);
1140    Property::link_two_way(p1.as_ref(), p2.as_ref());
1141    assert_eq!(p1.as_ref().get(), 88);
1142    assert_eq!(p2.as_ref().get(), 88);
1143    assert_eq!(depends.as_ref().get(), 88 + 8);
1144    p2.as_ref().set(5);
1145    assert_eq!(p1.as_ref().get(), 5);
1146    assert_eq!(p2.as_ref().get(), 5);
1147    assert_eq!(depends.as_ref().get(), 5 + 8);
1148    p1.as_ref().set(22);
1149    assert_eq!(p1.as_ref().get(), 22);
1150    assert_eq!(p2.as_ref().get(), 22);
1151    assert_eq!(depends.as_ref().get(), 22 + 8);
1152}
1153
1154#[test]
1155fn property_two_ways_test_binding() {
1156    let p1 = Rc::pin(Property::new(42));
1157    let p2 = Rc::pin(Property::new(88));
1158    let global = Rc::pin(Property::new(23));
1159    p2.as_ref().set_binding({
1160        let global = global.clone();
1161        move || global.as_ref().get() + 9
1162    });
1163
1164    let depends = Box::pin(Property::new(0));
1165    depends.as_ref().set_binding({
1166        let p1 = p1.clone();
1167        move || p1.as_ref().get() + 8
1168    });
1169
1170    Property::link_two_way(p1.as_ref(), p2.as_ref());
1171    assert_eq!(p1.as_ref().get(), 23 + 9);
1172    assert_eq!(p2.as_ref().get(), 23 + 9);
1173    assert_eq!(depends.as_ref().get(), 23 + 9 + 8);
1174    global.as_ref().set(55);
1175    assert_eq!(p1.as_ref().get(), 55 + 9);
1176    assert_eq!(p2.as_ref().get(), 55 + 9);
1177    assert_eq!(depends.as_ref().get(), 55 + 9 + 8);
1178}
1179
1180#[test]
1181fn property_two_ways_recurse_from_binding() {
1182    let xx = Rc::pin(Property::new(0));
1183
1184    let p1 = Rc::pin(Property::new(42));
1185    let p2 = Rc::pin(Property::new(88));
1186    let global = Rc::pin(Property::new(23));
1187
1188    let done = Rc::new(Cell::new(false));
1189    xx.set_binding({
1190        let p1 = p1.clone();
1191        let p2 = p2.clone();
1192        let global = global.clone();
1193        let xx_weak = pin_weak::rc::PinWeak::downgrade(xx.clone());
1194        move || {
1195            if !done.get() {
1196                done.set(true);
1197                Property::link_two_way(p1.as_ref(), p2.as_ref());
1198                let xx_weak = xx_weak.clone();
1199                p1.as_ref().set_binding(move || xx_weak.upgrade().unwrap().as_ref().get() + 9);
1200            }
1201            global.as_ref().get() + 2
1202        }
1203    });
1204    assert_eq!(xx.as_ref().get(), 23 + 2);
1205    assert_eq!(p1.as_ref().get(), 23 + 2 + 9);
1206    assert_eq!(p2.as_ref().get(), 23 + 2 + 9);
1207
1208    global.as_ref().set(55);
1209    assert_eq!(p1.as_ref().get(), 55 + 2 + 9);
1210    assert_eq!(p2.as_ref().get(), 55 + 2 + 9);
1211    assert_eq!(xx.as_ref().get(), 55 + 2);
1212}
1213
1214#[test]
1215fn property_two_ways_binding_of_two_way_binding_first() {
1216    let p1_1 = Rc::pin(Property::new(2));
1217    let p1_2 = Rc::pin(Property::new(4));
1218    Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1219
1220    assert_eq!(p1_1.as_ref().get(), 4);
1221    assert_eq!(p1_2.as_ref().get(), 4);
1222
1223    let p2 = Rc::pin(Property::new(3));
1224    Property::link_two_way(p1_1.as_ref(), p2.as_ref());
1225
1226    assert_eq!(p1_1.as_ref().get(), 3);
1227    assert_eq!(p1_2.as_ref().get(), 3);
1228    assert_eq!(p2.as_ref().get(), 3);
1229
1230    p1_1.set(6);
1231
1232    assert_eq!(p1_1.as_ref().get(), 6);
1233    assert_eq!(p1_2.as_ref().get(), 6);
1234    assert_eq!(p2.as_ref().get(), 6);
1235
1236    p1_2.set(8);
1237
1238    assert_eq!(p1_1.as_ref().get(), 8);
1239    assert_eq!(p1_2.as_ref().get(), 8);
1240    assert_eq!(p2.as_ref().get(), 8);
1241
1242    p2.set(7);
1243
1244    assert_eq!(p1_1.as_ref().get(), 7);
1245    assert_eq!(p1_2.as_ref().get(), 7);
1246    assert_eq!(p2.as_ref().get(), 7);
1247}
1248
1249#[test]
1250fn property_two_ways_binding_of_two_way_binding_second() {
1251    let p1 = Rc::pin(Property::new(2));
1252    let p2_1 = Rc::pin(Property::new(3));
1253    let p2_2 = Rc::pin(Property::new(5));
1254    Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1255
1256    assert_eq!(p2_1.as_ref().get(), 5);
1257    assert_eq!(p2_2.as_ref().get(), 5);
1258
1259    Property::link_two_way(p1.as_ref(), p2_2.as_ref());
1260
1261    assert_eq!(p1.as_ref().get(), 5);
1262    assert_eq!(p2_1.as_ref().get(), 5);
1263    assert_eq!(p2_2.as_ref().get(), 5);
1264
1265    p1.set(6);
1266
1267    assert_eq!(p1.as_ref().get(), 6);
1268    assert_eq!(p2_1.as_ref().get(), 6);
1269    assert_eq!(p2_2.as_ref().get(), 6);
1270
1271    p2_1.set(7);
1272
1273    assert_eq!(p1.as_ref().get(), 7);
1274    assert_eq!(p2_1.as_ref().get(), 7);
1275    assert_eq!(p2_2.as_ref().get(), 7);
1276
1277    p2_2.set(9);
1278
1279    assert_eq!(p1.as_ref().get(), 9);
1280    assert_eq!(p2_1.as_ref().get(), 9);
1281    assert_eq!(p2_2.as_ref().get(), 9);
1282}
1283
1284#[test]
1285fn property_two_ways_binding_of_two_two_way_bindings() {
1286    let p1_1 = Rc::pin(Property::new(2));
1287    let p1_2 = Rc::pin(Property::new(4));
1288    Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1289    assert_eq!(p1_1.as_ref().get(), 4);
1290    assert_eq!(p1_2.as_ref().get(), 4);
1291
1292    let p2_1 = Rc::pin(Property::new(3));
1293    let p2_2 = Rc::pin(Property::new(5));
1294    Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1295
1296    assert_eq!(p2_1.as_ref().get(), 5);
1297    assert_eq!(p2_2.as_ref().get(), 5);
1298
1299    Property::link_two_way(p1_1.as_ref(), p2_2.as_ref());
1300
1301    assert_eq!(p1_1.as_ref().get(), 5);
1302    assert_eq!(p1_2.as_ref().get(), 5);
1303    assert_eq!(p2_1.as_ref().get(), 5);
1304    assert_eq!(p2_2.as_ref().get(), 5);
1305
1306    p1_1.set(6);
1307    assert_eq!(p1_1.as_ref().get(), 6);
1308    assert_eq!(p1_2.as_ref().get(), 6);
1309    assert_eq!(p2_1.as_ref().get(), 6);
1310    assert_eq!(p2_2.as_ref().get(), 6);
1311
1312    p1_2.set(8);
1313    assert_eq!(p1_1.as_ref().get(), 8);
1314    assert_eq!(p1_2.as_ref().get(), 8);
1315    assert_eq!(p2_1.as_ref().get(), 8);
1316    assert_eq!(p2_2.as_ref().get(), 8);
1317
1318    p2_1.set(7);
1319    assert_eq!(p1_1.as_ref().get(), 7);
1320    assert_eq!(p1_2.as_ref().get(), 7);
1321    assert_eq!(p2_1.as_ref().get(), 7);
1322    assert_eq!(p2_2.as_ref().get(), 7);
1323
1324    p2_2.set(9);
1325    assert_eq!(p1_1.as_ref().get(), 9);
1326    assert_eq!(p1_2.as_ref().get(), 9);
1327    assert_eq!(p2_1.as_ref().get(), 9);
1328    assert_eq!(p2_2.as_ref().get(), 9);
1329}
1330
1331mod change_tracker;
1332pub use change_tracker::*;
1333mod properties_animations;
1334pub use crate::items::StateInfo;
1335pub use properties_animations::*;
1336
1337struct StateInfoBinding<F> {
1338    dirty_time: Cell<Option<crate::animations::Instant>>,
1339    binding: F,
1340}
1341
1342unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable for StateInfoBinding<F> {
1343    unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
1344        let value = &mut *(value as *mut StateInfo);
1346        let new_state = (self.binding)();
1347        let timestamp = self.dirty_time.take();
1348        if new_state != value.current_state {
1349            value.previous_state = value.current_state;
1350            value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1351            value.current_state = new_state;
1352        }
1353        BindingResult::KeepBinding
1354    }
1355
1356    fn mark_dirty(self: Pin<&Self>) {
1357        if self.dirty_time.get().is_none() {
1358            self.dirty_time.set(Some(crate::animations::current_tick()))
1359        }
1360    }
1361}
1362
1363pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
1365    let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding };
1366    unsafe {
1368        property.handle.set_binding(
1369            bind_callable,
1370            #[cfg(slint_debug_property)]
1371            property.debug_name.borrow().as_str(),
1372        )
1373    }
1374}
1375
1376#[doc(hidden)]
1377pub trait PropertyDirtyHandler {
1378    fn notify(self: Pin<&Self>);
1379}
1380
1381impl PropertyDirtyHandler for () {
1382    fn notify(self: Pin<&Self>) {}
1383}
1384
1385impl<F: Fn()> PropertyDirtyHandler for F {
1386    fn notify(self: Pin<&Self>) {
1387        (self.get_ref())()
1388    }
1389}
1390
1391pub struct PropertyTracker<DirtyHandler = ()> {
1394    holder: BindingHolder<DirtyHandler>,
1395}
1396
1397impl Default for PropertyTracker<()> {
1398    fn default() -> Self {
1399        static VT: &BindingVTable = &BindingVTable {
1400            drop: |_| (),
1401            evaluate: |_, _| BindingResult::KeepBinding,
1402            mark_dirty: |_, _| (),
1403            intercept_set: |_, _| false,
1404            intercept_set_binding: |_, _| false,
1405        };
1406
1407        let holder = BindingHolder {
1408            dependencies: Cell::new(0),
1409            dep_nodes: Default::default(),
1410            vtable: VT,
1411            dirty: Cell::new(true), is_two_way_binding: false,
1413            pinned: PhantomPinned,
1414            binding: (),
1415            #[cfg(slint_debug_property)]
1416            debug_name: "<PropertyTracker<()>>".into(),
1417        };
1418        Self { holder }
1419    }
1420}
1421
1422impl<DirtyHandler> Drop for PropertyTracker<DirtyHandler> {
1423    fn drop(&mut self) {
1424        unsafe {
1425            DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1426        }
1427    }
1428}
1429
1430impl<DirtyHandler: PropertyDirtyHandler> PropertyTracker<DirtyHandler> {
1431    #[cfg(slint_debug_property)]
1432    pub fn set_debug_name(&mut self, debug_name: alloc::string::String) {
1434        self.holder.debug_name = debug_name;
1435    }
1436
1437    pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1439        if CURRENT_BINDING.is_set() {
1440            CURRENT_BINDING.with(|cur_binding| {
1441                if let Some(cur_binding) = cur_binding {
1442                    debug_assert!(!core::ptr::eq(
1443                        self.holder.dependencies.get() as *const u32,
1444                        (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
1445                    ));
1446                    cur_binding.register_self_as_dependency(
1447                        self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1448                        #[cfg(slint_debug_property)]
1449                        &self.holder.debug_name,
1450                    );
1451                }
1452            });
1453        }
1454    }
1455
1456    pub fn is_dirty(&self) -> bool {
1459        self.holder.dirty.get()
1460    }
1461
1462    pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1466        self.register_as_dependency_to_current_binding();
1467        self.evaluate_as_dependency_root(f)
1468    }
1469
1470    pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1474        self.holder.dep_nodes.set(Default::default());
1476
1477        let pinned_holder = unsafe {
1479            self.map_unchecked(|s| {
1480                core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder)
1481            })
1482        };
1483        let r = CURRENT_BINDING.set(Some(pinned_holder), f);
1484        self.holder.dirty.set(false);
1485        r
1486    }
1487
1488    pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1491        self.register_as_dependency_to_current_binding();
1492        self.is_dirty().then(|| self.evaluate_as_dependency_root(f))
1493    }
1494
1495    pub fn set_dirty(&self) {
1497        self.holder.dirty.set(true);
1498        unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1499    }
1500
1501    pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1512        unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1514            _self: *const BindingHolder,
1515            was_dirty: bool,
1516        ) {
1517            if !was_dirty {
1518                Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify();
1519            }
1520        }
1521
1522        trait HasBindingVTable {
1523            const VT: &'static BindingVTable;
1524        }
1525        impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1526            const VT: &'static BindingVTable = &BindingVTable {
1527                drop: |_| (),
1528                evaluate: |_, _| BindingResult::KeepBinding,
1529                mark_dirty: mark_dirty::<B>,
1530                intercept_set: |_, _| false,
1531                intercept_set_binding: |_, _| false,
1532            };
1533        }
1534
1535        let holder = BindingHolder {
1536            dependencies: Cell::new(0),
1537            dep_nodes: Default::default(),
1538            vtable: <DirtyHandler as HasBindingVTable>::VT,
1539            dirty: Cell::new(true), is_two_way_binding: false,
1541            pinned: PhantomPinned,
1542            binding: handler,
1543            #[cfg(slint_debug_property)]
1544            debug_name: "<PropertyTracker>".into(),
1545        };
1546        Self { holder }
1547    }
1548}
1549
1550#[test]
1551fn test_property_listener_scope() {
1552    let scope = Box::pin(PropertyTracker::default());
1553    let prop1 = Box::pin(Property::new(42));
1554    assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1557    assert_eq!(r, 42);
1558    assert!(!scope.is_dirty()); prop1.as_ref().set(88);
1560    assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1562    assert_eq!(r, 89);
1563    assert!(!scope.is_dirty());
1564    let r = scope.as_ref().evaluate(|| 12);
1565    assert_eq!(r, 12);
1566    assert!(!scope.is_dirty());
1567    prop1.as_ref().set(1);
1568    assert!(!scope.is_dirty());
1569    scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1570    scope.set_dirty();
1571    let mut ok = false;
1572    scope.as_ref().evaluate_if_dirty(|| ok = true);
1573    assert!(ok);
1574}
1575
1576#[test]
1577fn test_nested_property_trackers() {
1578    let tracker1 = Box::pin(PropertyTracker::default());
1579    let tracker2 = Box::pin(PropertyTracker::default());
1580    let prop = Box::pin(Property::new(42));
1581
1582    let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1583    assert_eq!(r, 42);
1584
1585    prop.as_ref().set(1);
1586    assert!(tracker2.as_ref().is_dirty());
1587    assert!(tracker1.as_ref().is_dirty());
1588
1589    let r = tracker1
1590        .as_ref()
1591        .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1592    assert_eq!(r, 1);
1593    prop.as_ref().set(100);
1594    assert!(tracker2.as_ref().is_dirty());
1595    assert!(!tracker1.as_ref().is_dirty());
1596}
1597
1598#[test]
1599fn test_property_dirty_handler() {
1600    let call_flag = Rc::new(Cell::new(false));
1601    let tracker = Box::pin(PropertyTracker::new_with_dirty_handler({
1602        let call_flag = call_flag.clone();
1603        move || {
1604            (*call_flag).set(true);
1605        }
1606    }));
1607    let prop = Box::pin(Property::new(42));
1608
1609    let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1610
1611    assert_eq!(r, 42);
1612    assert!(!tracker.as_ref().is_dirty());
1613    assert!(!call_flag.get());
1614
1615    prop.as_ref().set(100);
1616    assert!(tracker.as_ref().is_dirty());
1617    assert!(call_flag.get());
1618
1619    call_flag.set(false);
1622    prop.as_ref().set(101);
1623    assert!(tracker.as_ref().is_dirty());
1624    assert!(!call_flag.get());
1625}
1626
1627#[test]
1628fn test_property_tracker_drop() {
1629    let outer_tracker = Box::pin(PropertyTracker::default());
1630    let inner_tracker = Box::pin(PropertyTracker::default());
1631    let prop = Box::pin(Property::new(42));
1632
1633    let r =
1634        outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1635    assert_eq!(r, 42);
1636
1637    drop(inner_tracker);
1638    prop.as_ref().set(200); }
1640
1641#[test]
1642fn test_nested_property_tracker_dirty() {
1643    let outer_tracker = Box::pin(PropertyTracker::default());
1644    let inner_tracker = Box::pin(PropertyTracker::default());
1645    let prop = Box::pin(Property::new(42));
1646
1647    let r =
1648        outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1649    assert_eq!(r, 42);
1650
1651    assert!(!outer_tracker.is_dirty());
1652    assert!(!inner_tracker.is_dirty());
1653
1654    inner_tracker.as_ref().set_dirty();
1657    assert!(outer_tracker.is_dirty());
1658}
1659
1660#[test]
1661#[allow(clippy::redundant_closure)]
1662fn test_nested_property_tracker_evaluate_if_dirty() {
1663    let outer_tracker = Box::pin(PropertyTracker::default());
1664    let inner_tracker = Box::pin(PropertyTracker::default());
1665    let prop = Box::pin(Property::new(42));
1666
1667    let mut cache = 0;
1668    let mut cache_or_evaluate = || {
1669        if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1670            cache = x;
1671        }
1672        cache
1673    };
1674    let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1675    assert_eq!(r, 43);
1676    assert!(!outer_tracker.is_dirty());
1677    assert!(!inner_tracker.is_dirty());
1678    prop.as_ref().set(11);
1679    assert!(outer_tracker.is_dirty());
1680    assert!(inner_tracker.is_dirty());
1681    let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1682    assert_eq!(r, 12);
1683}
1684
1685#[cfg(feature = "ffi")]
1686pub(crate) mod ffi;