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;