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 unsafe {
119 (*to).0.set((*from).0.get());
120 if let Some(next) = (*from).0.get().as_ref() {
121 debug_assert_eq!(from as *const _, next.prev.get() as *const _);
122 next.debug_assert_valid();
123 next.prev.set(to as *const _);
124 next.debug_assert_valid();
125 }
126 }
127 }
128
129 pub fn swap(from: Pin<&Self>, to: Pin<&Self>) {
131 Cell::swap(&from.0, &to.0);
132 unsafe {
133 if let Some(n) = from.0.get().as_ref() {
134 debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _);
135 n.prev.set(&from.0 as *const _);
136 n.debug_assert_valid();
137 }
138
139 if let Some(n) = to.0.get().as_ref() {
140 debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _);
141 n.prev.set(&to.0 as *const _);
142 n.debug_assert_valid();
143 }
144 }
145 }
146
147 pub fn is_empty(&self) -> bool {
149 self.0.get().is_null()
150 }
151
152 pub unsafe fn drop(_self: *mut Self) {
153 unsafe {
154 if let Some(next) = (*_self).0.get().as_ref() {
155 debug_assert_eq!(_self as *const _, next.prev.get() as *const _);
156 next.debug_assert_valid();
157 next.prev.set(core::ptr::null());
158 next.debug_assert_valid();
159 }
160 }
161 }
162 pub fn append(&self, node: Pin<&DependencyNode<T>>) {
163 unsafe {
164 node.remove();
165 node.debug_assert_valid();
166 let old = self.0.get();
167 if let Some(x) = old.as_ref() {
168 x.debug_assert_valid();
169 }
170 self.0.set(node.get_ref() as *const DependencyNode<_>);
171 node.next.set(old);
172 node.prev.set(&self.0 as *const _);
173 if let Some(old) = old.as_ref() {
174 old.prev.set((&node.next) as *const _);
175 old.debug_assert_valid();
176 }
177 node.debug_assert_valid();
178 }
179 }
180
181 pub fn for_each(&self, mut f: impl FnMut(&T)) {
182 unsafe {
183 let mut next = self.0.get();
184 while let Some(node) = next.as_ref() {
185 node.debug_assert_valid();
186 next = node.next.get();
187 f(&node.binding);
188 }
189 }
190 }
191
192 pub fn take_head(&self) -> Option<T>
194 where
195 T: Copy,
196 {
197 unsafe {
198 if let Some(node) = self.0.get().as_ref() {
199 node.debug_assert_valid();
200 node.remove();
201 Some(node.binding)
202 } else {
203 None
204 }
205 }
206 }
207 }
208
209 pub struct DependencyNode<T> {
212 next: Cell<*const DependencyNode<T>>,
213 prev: Cell<*const Cell<*const DependencyNode<T>>>,
215 binding: T,
216 }
217
218 impl<T> DependencyNode<T> {
219 pub fn new(binding: T) -> Self {
220 Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding }
221 }
222
223 pub fn debug_assert_valid(&self) {
225 unsafe {
226 debug_assert!(
227 self.prev.get().is_null()
228 || (*self.prev.get()).get() == self as *const DependencyNode<T>
229 );
230 debug_assert!(
231 self.next.get().is_null()
232 || (*self.next.get()).prev.get()
233 == (&self.next) as *const Cell<*const DependencyNode<T>>
234 );
235 debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>);
237 debug_assert_ne!(
238 self.prev.get(),
239 (&self.next) as *const Cell<*const DependencyNode<T>>
240 );
241 }
242 }
243
244 pub fn remove(&self) {
245 self.debug_assert_valid();
246 unsafe {
247 if let Some(prev) = self.prev.get().as_ref() {
248 prev.set(self.next.get());
249 }
250 if let Some(next) = self.next.get().as_ref() {
251 next.debug_assert_valid();
252 next.prev.set(self.prev.get());
253 next.debug_assert_valid();
254 }
255 }
256 self.prev.set(core::ptr::null());
257 self.next.set(core::ptr::null());
258 }
259 }
260
261 impl<T> Drop for DependencyNode<T> {
262 fn drop(&mut self) {
263 self.remove();
264 }
265 }
266}
267
268type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>;
269type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>;
270
271use alloc::boxed::Box;
272use alloc::rc::Rc;
273use core::cell::{Cell, RefCell, UnsafeCell};
274use core::marker::{PhantomData, PhantomPinned};
275use core::pin::Pin;
276
277static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
280
281#[derive(Copy, Clone, Debug, Eq, PartialEq)]
283enum BindingResult {
284 KeepBinding,
286 RemoveBinding,
289}
290
291struct BindingVTable {
292 drop: unsafe fn(_self: *mut BindingHolder),
293 evaluate: unsafe fn(_self: *const BindingHolder, value: *mut ()) -> BindingResult,
294 mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool),
295 intercept_set: unsafe fn(_self: *const BindingHolder, value: *const ()) -> bool,
296 intercept_set_binding:
297 unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool,
298}
299
300unsafe trait BindingCallable<T> {
306 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult;
309
310 fn mark_dirty(self: Pin<&Self>) {}
313
314 fn intercept_set(self: Pin<&Self>, _value: &T) -> bool {
320 false
321 }
322
323 unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
327 false
328 }
329
330 const IS_TWO_WAY_BINDING: bool = false;
332}
333
334unsafe impl<T, F: Fn(&mut T) -> BindingResult> BindingCallable<T> for F {
335 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
336 self(value)
337 }
338}
339
340#[cfg(feature = "std")]
341use std::thread_local;
342#[cfg(feature = "std")]
343scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Option<Pin<&'a BindingHolder>>);
344
345#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
346mod unsafe_single_threaded {
347 use super::BindingHolder;
348 use core::cell::Cell;
349 use core::pin::Pin;
350 use core::ptr::null;
351 pub(super) struct FakeThreadStorage(Cell<*const BindingHolder>);
352 impl FakeThreadStorage {
353 pub const fn new() -> Self {
354 Self(Cell::new(null()))
355 }
356 pub fn set<T>(&self, value: Option<Pin<&BindingHolder>>, f: impl FnOnce() -> T) -> T {
357 let old = self.0.replace(value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
358 let res = f();
359 let new = self.0.replace(old);
360 assert_eq!(new, value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
361 res
362 }
363 pub fn is_set(&self) -> bool {
364 !self.0.get().is_null()
365 }
366 pub fn with<T>(&self, f: impl FnOnce(Option<Pin<&BindingHolder>>) -> T) -> T {
367 let local = unsafe { self.0.get().as_ref().map(|x| Pin::new_unchecked(x)) };
368 let res = f(local);
369 assert_eq!(self.0.get(), local.map_or(null(), |v| v.get_ref() as *const BindingHolder));
370 res
371 }
372 }
373 unsafe impl Send for FakeThreadStorage {}
375 unsafe impl Sync for FakeThreadStorage {}
376}
377#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
378static CURRENT_BINDING: unsafe_single_threaded::FakeThreadStorage =
379 unsafe_single_threaded::FakeThreadStorage::new();
380
381pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
384 CURRENT_BINDING.set(None, f)
385}
386
387pub fn is_currently_tracking() -> bool {
390 CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x| x.is_some())
391}
392
393#[repr(C)]
395struct BindingHolder<B = ()> {
396 dependencies: Cell<usize>,
398 dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
401 vtable: &'static BindingVTable,
402 dirty: Cell<bool>,
404 is_two_way_binding: bool,
406 pinned: PhantomPinned,
407 #[cfg(slint_debug_property)]
408 pub debug_name: alloc::string::String,
409
410 binding: B,
411}
412
413impl BindingHolder {
414 fn register_self_as_dependency(
415 self: Pin<&Self>,
416 property_that_will_notify: *mut DependencyListHead,
417 #[cfg(slint_debug_property)] _other_debug_name: &str,
418 ) {
419 let node = DependencyNode::new(self.get_ref() as *const _);
420 let mut dep_nodes = self.dep_nodes.take();
421 let node = dep_nodes.push_front(node);
422 unsafe { DependencyListHead::append(&*property_that_will_notify, node) }
423 self.dep_nodes.set(dep_nodes);
424 }
425}
426
427fn alloc_binding_holder<T, B: BindingCallable<T> + 'static>(binding: B) -> *mut BindingHolder {
428 unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
430 unsafe {
431 drop(Box::from_raw(_self as *mut BindingHolder<B>));
432 }
433 }
434
435 unsafe fn evaluate<T, B: BindingCallable<T>>(
438 _self: *const BindingHolder,
439 value: *mut (),
440 ) -> BindingResult {
441 unsafe {
442 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
443 .evaluate(&mut *(value as *mut T))
444 }
445 }
446
447 unsafe fn mark_dirty<T, B: BindingCallable<T>>(_self: *const BindingHolder, _: bool) {
449 unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() }
450 }
451
452 unsafe fn intercept_set<T, B: BindingCallable<T>>(
454 _self: *const BindingHolder,
455 value: *const (),
456 ) -> bool {
457 unsafe {
458 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
459 .intercept_set(&*(value as *const T))
460 }
461 }
462
463 unsafe fn intercept_set_binding<T, B: BindingCallable<T>>(
464 _self: *const BindingHolder,
465 new_binding: *mut BindingHolder,
466 ) -> bool {
467 unsafe {
468 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
469 .intercept_set_binding(new_binding)
470 }
471 }
472
473 trait HasBindingVTable<T> {
474 const VT: &'static BindingVTable;
475 }
476 impl<T, B: BindingCallable<T>> HasBindingVTable<T> for B {
477 const VT: &'static BindingVTable = &BindingVTable {
478 drop: binding_drop::<B>,
479 evaluate: evaluate::<T, B>,
480 mark_dirty: mark_dirty::<T, B>,
481 intercept_set: intercept_set::<T, B>,
482 intercept_set_binding: intercept_set_binding::<T, B>,
483 };
484 }
485
486 let holder: BindingHolder<B> = BindingHolder {
487 dependencies: Cell::new(0),
488 dep_nodes: Default::default(),
489 vtable: <B as HasBindingVTable<T>>::VT,
490 dirty: Cell::new(true), is_two_way_binding: B::IS_TWO_WAY_BINDING,
492 pinned: PhantomPinned,
493 #[cfg(slint_debug_property)]
494 debug_name: Default::default(),
495 binding,
496 };
497 Box::into_raw(Box::new(holder)) as *mut BindingHolder
498}
499
500#[repr(transparent)]
501#[derive(Default)]
502struct PropertyHandle {
503 handle: Cell<usize>,
510}
511
512const BINDING_BORROWED: usize = 0b01;
513const BINDING_POINTER_TO_BINDING: usize = 0b10;
514const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED);
515
516impl core::fmt::Debug for PropertyHandle {
517 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
518 let handle = self.handle.get();
519 write!(
520 f,
521 "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}",
522 handle & !0b11,
523 self.lock_flag(),
524 PropertyHandle::is_pointer_to_binding(handle)
525 )
526 }
527}
528
529impl PropertyHandle {
530 #[inline]
532 fn lock_flag(&self) -> bool {
533 self.handle.get() & BINDING_BORROWED == BINDING_BORROWED
534 }
535 unsafe fn set_lock_flag(&self, set: bool) {
538 self.handle.set(if set {
539 self.handle.get() | BINDING_BORROWED
540 } else {
541 self.handle.get() & !BINDING_BORROWED
542 })
543 }
544
545 #[inline]
546 fn is_pointer_to_binding(handle: usize) -> bool {
547 handle & BINDING_POINTER_TO_BINDING == BINDING_POINTER_TO_BINDING
548 }
549
550 #[inline]
552 fn pointer_to_binding(handle: usize) -> Option<*mut BindingHolder> {
553 if Self::is_pointer_to_binding(handle) {
554 Some((handle & BINDING_POINTER_MASK) as *mut BindingHolder)
555 } else {
556 None
557 }
558 }
559
560 #[inline]
563 fn has_no_binding_or_lock(handle: usize) -> bool {
564 (handle as usize) & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0
565 }
566
567 fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
570 #[cfg(slint_debug_property)]
571 if self.lock_flag() {
572 unsafe {
573 let handle = self.handle.get();
574 if let Some(binding_pointer) = Self::pointer_to_binding(handle) {
575 let binding = &mut *(binding_pointer);
576 let debug_name = &binding.debug_name;
577 panic!("Recursion detected with property {debug_name}");
578 }
579 }
580 }
581 assert!(!self.lock_flag(), "Recursion detected");
582 unsafe {
583 self.set_lock_flag(true);
584 scopeguard::defer! { self.set_lock_flag(false); }
585 let handle = self.handle.get();
586 let binding = if let Some(pointer) = Self::pointer_to_binding(handle) {
587 Some(Pin::new_unchecked(&mut *(pointer)))
588 } else {
589 None
590 };
591 f(binding)
592 }
593 }
594
595 fn remove_binding(&self) {
596 assert!(!self.lock_flag(), "Recursion detected");
597
598 if let Some(binding) = Self::pointer_to_binding(self.handle.get()) {
599 unsafe {
600 self.set_lock_flag(true);
601 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
602 if (*binding).dependencies.get() == const_sentinel {
603 self.handle.set(const_sentinel);
604 (*binding).dependencies.set(0);
605 } else {
606 DependencyListHead::mem_move(
607 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
608 self.handle.as_ptr() as *mut DependencyListHead,
609 );
610 }
611 ((*binding).vtable.drop)(binding);
612 }
613 }
614 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
615 }
616
617 unsafe fn set_binding<T, B: BindingCallable<T> + 'static>(
619 &self,
620 binding: B,
621 #[cfg(slint_debug_property)] debug_name: &str,
622 ) {
623 let binding = alloc_binding_holder::<T, B>(binding);
624 #[cfg(slint_debug_property)]
625 unsafe {
626 (*binding).debug_name = debug_name.into();
627 }
628 self.set_binding_impl(binding);
629 }
630
631 fn set_binding_impl(&self, binding: *mut BindingHolder) {
633 let previous_binding_intercepted = self.access(|b| {
634 b.is_some_and(|b| unsafe {
635 (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
637 })
638 });
639
640 if previous_binding_intercepted {
641 return;
642 }
643
644 self.remove_binding();
645 debug_assert!(Self::has_no_binding_or_lock(binding as usize));
646 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
647 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
648 let is_constant = self.handle.get() == const_sentinel;
649 unsafe {
650 if is_constant {
651 (*binding).dependencies.set(const_sentinel);
652 } else {
653 DependencyListHead::mem_move(
654 self.handle.as_ptr() as *mut DependencyListHead,
655 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
656 );
657 }
658 }
659 self.handle.set((binding as usize) | BINDING_POINTER_TO_BINDING);
660 if !is_constant {
661 self.mark_dirty(
662 #[cfg(slint_debug_property)]
663 "",
664 );
665 }
666 }
667
668 fn dependencies(&self) -> *mut DependencyListHead {
669 assert!(!self.lock_flag(), "Recursion detected");
670 if Self::is_pointer_to_binding(self.handle.get()) {
671 self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
672 } else {
673 self.handle.as_ptr() as *mut DependencyListHead
674 }
675 }
676
677 unsafe fn update<T>(&self, value: *mut T) {
680 let remove = self.access(|binding| {
681 if let Some(binding) = binding {
682 if binding.dirty.get() {
683 unsafe fn evaluate_as_current_binding(
684 value: *mut (),
685 binding: Pin<&BindingHolder>,
686 ) -> BindingResult {
687 CURRENT_BINDING.set(Some(binding), || unsafe {
688 (binding.vtable.evaluate)(
689 binding.get_ref() as *const BindingHolder,
690 value as *mut (),
691 )
692 })
693 }
694
695 binding.dep_nodes.set(Default::default());
697 let r =
698 unsafe { evaluate_as_current_binding(value as *mut (), binding.as_ref()) };
699 binding.dirty.set(false);
700 if r == BindingResult::RemoveBinding {
701 return true;
702 }
703 }
704 }
705 false
706 });
707 if remove {
708 self.remove_binding()
709 }
710 }
711
712 fn register_as_dependency_to_current_binding(
714 self: Pin<&Self>,
715 #[cfg(slint_debug_property)] debug_name: &str,
716 ) {
717 if CURRENT_BINDING.is_set() {
718 CURRENT_BINDING.with(|cur_binding| {
719 if let Some(cur_binding) = cur_binding {
720 let dependencies = self.dependencies();
721 if !core::ptr::eq(
722 unsafe { *(dependencies as *mut *const u32) },
723 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
724 ) {
725 cur_binding.register_self_as_dependency(
726 dependencies,
727 #[cfg(slint_debug_property)]
728 debug_name,
729 );
730 }
731 }
732 });
733 }
734 }
735
736 fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
737 #[cfg(not(slint_debug_property))]
738 let debug_name = "";
739 unsafe {
740 let dependencies = self.dependencies();
741 assert!(
742 !core::ptr::eq(
743 *(dependencies as *mut *const u32),
744 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
745 ),
746 "Constant property being changed {debug_name}"
747 );
748 mark_dependencies_dirty(dependencies)
749 };
750 }
751
752 fn set_constant(&self) {
753 unsafe {
754 let dependencies = self.dependencies();
755 if !core::ptr::eq(
756 *(dependencies as *mut *const u32),
757 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
758 ) {
759 DependencyListHead::drop(dependencies);
760 *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32
761 }
762 }
763 }
764
765 fn is_constant(&self) -> bool {
766 let dependencies = self.dependencies();
767 core::ptr::eq(
768 unsafe { *(dependencies as *mut *const u32) },
771 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
772 )
773 }
774}
775
776impl Drop for PropertyHandle {
777 fn drop(&mut self) {
778 self.remove_binding();
779 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
780 if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 {
781 unsafe {
782 DependencyListHead::drop(self.handle.as_ptr() as *mut _);
783 }
784 }
785 }
786}
787
788unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
790 unsafe {
791 debug_assert!(!core::ptr::eq(
792 *(dependencies as *mut *const u32),
793 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
794 ));
795 DependencyListHead::for_each(&*dependencies, |binding| {
796 let binding: &BindingHolder = &**binding;
797 let was_dirty = binding.dirty.replace(true);
798 (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
799
800 assert!(
801 !core::ptr::eq(
802 *(binding.dependencies.as_ptr() as *mut *const u32),
803 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
804 ),
805 "Const property marked as dirty"
806 );
807
808 if !was_dirty {
809 mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead)
810 }
811 });
812 }
813}
814
815pub trait Binding<T> {
817 fn evaluate(&self, old_value: &T) -> T;
819}
820
821impl<T, F: Fn() -> T> Binding<T> for F {
822 fn evaluate(&self, _value: &T) -> T {
823 self()
824 }
825}
826
827#[repr(C)]
836pub struct Property<T> {
837 handle: PropertyHandle,
839 value: UnsafeCell<T>,
841 pinned: PhantomPinned,
842 #[cfg(slint_debug_property)]
846 pub debug_name: RefCell<alloc::string::String>,
847}
848
849impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
850 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
851 #[cfg(slint_debug_property)]
852 write!(f, "[{}]=", self.debug_name.borrow())?;
853 write!(
854 f,
855 "Property({:?}{})",
856 self.get_internal(),
857 if self.is_dirty() { " (dirty)" } else { "" }
858 )
859 }
860}
861
862impl<T: Default> Default for Property<T> {
863 fn default() -> Self {
864 Self {
865 handle: Default::default(),
866 value: Default::default(),
867 pinned: PhantomPinned,
868 #[cfg(slint_debug_property)]
869 debug_name: Default::default(),
870 }
871 }
872}
873
874impl<T: Clone> Property<T> {
875 pub fn new(value: T) -> Self {
877 Self {
878 handle: Default::default(),
879 value: UnsafeCell::new(value),
880 pinned: PhantomPinned,
881 #[cfg(slint_debug_property)]
882 debug_name: Default::default(),
883 }
884 }
885
886 pub fn new_named(value: T, _name: &'static str) -> Self {
888 Self {
889 handle: Default::default(),
890 value: UnsafeCell::new(value),
891 pinned: PhantomPinned,
892 #[cfg(slint_debug_property)]
893 debug_name: RefCell::new(_name.into()),
894 }
895 }
896
897 pub fn get(self: Pin<&Self>) -> T {
907 unsafe { self.handle.update(self.value.get()) };
908 let handle = unsafe { Pin::new_unchecked(&self.handle) };
909 handle.register_as_dependency_to_current_binding(
910 #[cfg(slint_debug_property)]
911 self.debug_name.borrow().as_str(),
912 );
913 self.get_internal()
914 }
915
916 pub fn get_untracked(self: Pin<&Self>) -> T {
938 unsafe { self.handle.update(self.value.get()) };
939 self.get_internal()
940 }
941
942 pub fn get_internal(&self) -> T {
944 self.handle.access(|_| {
945 unsafe { (*self.value.get()).clone() }
947 })
948 }
949
950 pub fn set(&self, t: T)
956 where
957 T: PartialEq,
958 {
959 let previous_binding_intercepted = self.handle.access(|b| {
960 b.is_some_and(|b| unsafe {
961 (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ())
963 })
964 });
965 if !previous_binding_intercepted {
966 self.handle.remove_binding();
967 }
968
969 let has_value_changed = self.handle.access(|_| unsafe {
971 *self.value.get() != t && {
972 *self.value.get() = t;
973 true
974 }
975 });
976 if has_value_changed {
977 self.handle.mark_dirty(
978 #[cfg(slint_debug_property)]
979 self.debug_name.borrow().as_str(),
980 );
981 }
982 }
983
984 pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
1011 unsafe {
1013 self.handle.set_binding(
1014 move |val: &mut T| {
1015 *val = binding.evaluate(val);
1016 BindingResult::KeepBinding
1017 },
1018 #[cfg(slint_debug_property)]
1019 self.debug_name.borrow().as_str(),
1020 )
1021 }
1022 self.handle.mark_dirty(
1023 #[cfg(slint_debug_property)]
1024 self.debug_name.borrow().as_str(),
1025 );
1026 }
1027
1028 pub fn is_dirty(&self) -> bool {
1031 self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
1032 }
1033
1034 pub fn mark_dirty(&self) {
1037 self.handle.mark_dirty(
1038 #[cfg(slint_debug_property)]
1039 self.debug_name.borrow().as_str(),
1040 )
1041 }
1042
1043 pub fn set_constant(&self) {
1045 self.handle.set_constant();
1046 }
1047
1048 pub fn is_constant(&self) -> bool {
1050 self.handle.is_constant()
1051 }
1052}
1053
1054#[test]
1055fn properties_simple_test() {
1056 use pin_weak::rc::PinWeak;
1057 use std::rc::Rc;
1058 fn g(prop: &Property<i32>) -> i32 {
1059 unsafe { Pin::new_unchecked(prop).get() }
1060 }
1061
1062 #[derive(Default)]
1063 struct Component {
1064 width: Property<i32>,
1065 height: Property<i32>,
1066 area: Property<i32>,
1067 }
1068
1069 let compo = Rc::pin(Component::default());
1070 let w = PinWeak::downgrade(compo.clone());
1071 compo.area.set_binding(move || {
1072 let compo = w.upgrade().unwrap();
1073 g(&compo.width) * g(&compo.height)
1074 });
1075 compo.width.set(4);
1076 compo.height.set(8);
1077 assert_eq!(g(&compo.width), 4);
1078 assert_eq!(g(&compo.height), 8);
1079 assert_eq!(g(&compo.area), 4 * 8);
1080
1081 let w = PinWeak::downgrade(compo.clone());
1082 compo.width.set_binding(move || {
1083 let compo = w.upgrade().unwrap();
1084 g(&compo.height) * 2
1085 });
1086 assert_eq!(g(&compo.width), 8 * 2);
1087 assert_eq!(g(&compo.height), 8);
1088 assert_eq!(g(&compo.area), 8 * 8 * 2);
1089}
1090
1091struct TwoWayBinding<T> {
1092 common_property: Pin<Rc<Property<T>>>,
1093}
1094unsafe impl<T: PartialEq + Clone + 'static> BindingCallable<T> for TwoWayBinding<T> {
1095 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
1096 *value = self.common_property.as_ref().get();
1097 BindingResult::KeepBinding
1098 }
1099
1100 fn intercept_set(self: Pin<&Self>, value: &T) -> bool {
1101 self.common_property.as_ref().set(value.clone());
1102 true
1103 }
1104
1105 unsafe fn intercept_set_binding(self: Pin<&Self>, new_binding: *mut BindingHolder) -> bool {
1106 self.common_property.handle.set_binding_impl(new_binding);
1107 true
1108 }
1109
1110 const IS_TWO_WAY_BINDING: bool = true;
1111}
1112
1113impl<T: PartialEq + Clone + 'static> Property<T> {
1114 pub(crate) fn check_common_property(self: Pin<&Self>) -> Option<Pin<Rc<Property<T>>>> {
1116 let handle_val = self.handle.handle.get();
1117 if let Some(holder) = PropertyHandle::pointer_to_binding(handle_val) {
1118 if unsafe { *&raw const (*holder).is_two_way_binding } {
1120 return Some(unsafe {
1122 (*(holder as *const BindingHolder<TwoWayBinding<T>>))
1123 .binding
1124 .common_property
1125 .clone()
1126 });
1127 }
1128 }
1129 None
1130 }
1131
1132 pub fn link_two_way(prop1: Pin<&Self>, prop2: Pin<&Self>) {
1136 #[cfg(slint_debug_property)]
1137 let debug_name =
1138 alloc::format!("<{}<=>{}>", prop1.debug_name.borrow(), prop2.debug_name.borrow());
1139
1140 let value = prop2.get_internal();
1141
1142 if let Some(common_property) = prop1.check_common_property() {
1143 unsafe {
1145 prop2.handle.set_binding(
1146 TwoWayBinding::<T> { common_property },
1147 #[cfg(slint_debug_property)]
1148 debug_name.as_str(),
1149 );
1150 }
1151 prop2.set(value);
1152 return;
1153 }
1154
1155 if let Some(common_property) = prop2.check_common_property() {
1156 unsafe {
1158 prop1.handle.set_binding(
1159 TwoWayBinding::<T> { common_property },
1160 #[cfg(slint_debug_property)]
1161 debug_name.as_str(),
1162 );
1163 }
1164 return;
1165 }
1166
1167 let prop2_handle_val = prop2.handle.handle.get();
1168 let handle = if PropertyHandle::is_pointer_to_binding(prop2_handle_val) {
1169 prop2.handle.handle.set(0);
1171 PropertyHandle { handle: Cell::new(prop2_handle_val) }
1172 } else {
1173 PropertyHandle::default()
1174 };
1175
1176 let common_property = Rc::pin(Property {
1177 handle,
1178 value: UnsafeCell::new(value),
1179 pinned: PhantomPinned,
1180 #[cfg(slint_debug_property)]
1181 debug_name: debug_name.clone().into(),
1182 });
1183 unsafe {
1185 prop1.handle.set_binding(
1186 TwoWayBinding { common_property: common_property.clone() },
1187 #[cfg(slint_debug_property)]
1188 debug_name.as_str(),
1189 );
1190 prop2.handle.set_binding(
1191 TwoWayBinding { common_property },
1192 #[cfg(slint_debug_property)]
1193 debug_name.as_str(),
1194 );
1195 }
1196 }
1197
1198 pub fn link_two_way_with_map<T2: PartialEq + Clone + 'static>(
1204 prop1: Pin<&Self>,
1205 prop2: Pin<&Property<T2>>,
1206 map_to: impl Fn(&T) -> T2 + Clone + 'static, map_from: impl Fn(&mut T, &T2) + Clone + 'static,
1208 ) {
1209 let common_property = if let Some(common_property) = prop1.check_common_property() {
1210 common_property
1211 } else {
1212 let prop1_handle_val = prop1.handle.handle.get();
1213 let handle = if PropertyHandle::is_pointer_to_binding(prop1_handle_val) {
1214 prop1.handle.handle.set(0);
1216 PropertyHandle { handle: Cell::new(prop1_handle_val) }
1217 } else {
1218 PropertyHandle::default()
1219 };
1220
1221 #[cfg(slint_debug_property)]
1222 let debug_name = alloc::format!("{}*", prop1.debug_name.borrow());
1223
1224 let common_property = Rc::pin(Property {
1225 handle,
1226 value: UnsafeCell::new(prop1.get_internal()),
1227 pinned: PhantomPinned,
1228 #[cfg(slint_debug_property)]
1229 debug_name: debug_name.clone().into(),
1230 });
1231 unsafe {
1233 prop1.handle.set_binding(
1234 TwoWayBinding::<T> { common_property: common_property.clone() },
1235 #[cfg(slint_debug_property)]
1236 debug_name.as_str(),
1237 );
1238 }
1239 common_property
1240 };
1241 Self::link_two_way_with_map_to_common_property(common_property, prop2, map_to, map_from);
1242 }
1243
1244 pub(crate) fn link_two_way_with_map_to_common_property<T2: PartialEq + Clone + 'static>(
1247 common_property: Pin<Rc<Self>>,
1248 prop2: Pin<&Property<T2>>,
1249 map_to: impl Fn(&T) -> T2 + Clone + 'static,
1250 map_from: impl Fn(&mut T, &T2) + Clone + 'static,
1251 ) {
1252 struct TwoWayBindingWithMap<T, T2, M1, M2> {
1253 common_property: Pin<Rc<Property<T>>>,
1254 map_to: M1,
1255 map_from: M2,
1256 marker: PhantomData<(T, T2)>,
1257 }
1258 unsafe impl<
1259 T: PartialEq + Clone + 'static,
1260 T2: PartialEq + Clone + 'static,
1261 M1: Fn(&T) -> T2 + Clone + 'static,
1262 M2: Fn(&mut T, &T2) + Clone + 'static,
1263 > BindingCallable<T2> for TwoWayBindingWithMap<T, T2, M1, M2>
1264 {
1265 fn evaluate(self: Pin<&Self>, value: &mut T2) -> BindingResult {
1266 *value = (self.map_to)(&self.common_property.as_ref().get());
1267 BindingResult::KeepBinding
1268 }
1269
1270 fn intercept_set(self: Pin<&Self>, value: &T2) -> bool {
1271 let mut old = self.common_property.as_ref().get();
1272 (self.map_from)(&mut old, value);
1273 self.common_property.as_ref().set(old);
1274 true
1275 }
1276
1277 unsafe fn intercept_set_binding(
1278 self: Pin<&Self>,
1279 new_binding: *mut BindingHolder,
1280 ) -> bool {
1281 let new_new_binding = alloc_binding_holder(BindingMapper::<T, T2, M1, M2> {
1282 b: new_binding,
1283 map_to: self.map_to.clone(),
1284 map_from: self.map_from.clone(),
1285 marker: PhantomData,
1286 });
1287 self.common_property.handle.set_binding_impl(new_new_binding);
1288 true
1289 }
1290 }
1291
1292 struct BindingMapper<T, T2, M1, M2> {
1294 b: *mut BindingHolder,
1296 map_to: M1,
1297 map_from: M2,
1298 marker: PhantomData<(T, T2)>,
1299 }
1300 unsafe impl<
1301 T: PartialEq + Clone + 'static,
1302 T2: PartialEq + Clone + 'static,
1303 M1: Fn(&T) -> T2 + 'static,
1304 M2: Fn(&mut T, &T2) + 'static,
1305 > BindingCallable<T> for BindingMapper<T, T2, M1, M2>
1306 {
1307 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
1308 let mut sub_value = (self.map_to)(value);
1309 unsafe {
1311 ((*self.b).vtable.evaluate)(self.b, &mut sub_value as *mut T2 as *mut ());
1312 }
1313 (self.map_from)(value, &sub_value);
1314 BindingResult::KeepBinding
1315 }
1316
1317 fn intercept_set(self: Pin<&Self>, value: &T) -> bool {
1318 let sub_value = (self.map_to)(value);
1319 unsafe {
1321 ((*self.b).vtable.intercept_set)(self.b, &sub_value as *const T2 as *const ())
1322 }
1323 }
1324 }
1325 impl<T, T2, M1, M2> Drop for BindingMapper<T, T2, M1, M2> {
1326 fn drop(&mut self) {
1327 unsafe {
1328 ((*self.b).vtable.drop)(self.b);
1329 }
1330 }
1331 }
1332
1333 #[cfg(slint_debug_property)]
1334 let debug_name = alloc::format!(
1335 "<{}<=>{}>",
1336 common_property.debug_name.borrow(),
1337 prop2.debug_name.borrow()
1338 );
1339
1340 let old_handle = prop2.handle.handle.get();
1341 let old_pointer = PropertyHandle::pointer_to_binding(old_handle);
1342 if old_pointer.is_some() {
1343 prop2.handle.handle.set(0);
1344 }
1345
1346 unsafe {
1347 prop2.handle.set_binding(
1348 TwoWayBindingWithMap { common_property, map_to, map_from, marker: PhantomData },
1349 #[cfg(slint_debug_property)]
1350 debug_name.as_str(),
1351 );
1352
1353 if let Some(binding) = old_pointer {
1354 prop2.handle.set_binding_impl(binding);
1355 }
1356 };
1357 }
1358}
1359
1360#[test]
1361fn property_two_ways_test() {
1362 let p1 = Rc::pin(Property::new(42));
1363 let p2 = Rc::pin(Property::new(88));
1364
1365 let depends = Box::pin(Property::new(0));
1366 depends.as_ref().set_binding({
1367 let p1 = p1.clone();
1368 move || p1.as_ref().get() + 8
1369 });
1370 assert_eq!(depends.as_ref().get(), 42 + 8);
1371 Property::link_two_way(p1.as_ref(), p2.as_ref());
1372 assert_eq!(p1.as_ref().get(), 88);
1373 assert_eq!(p2.as_ref().get(), 88);
1374 assert_eq!(depends.as_ref().get(), 88 + 8);
1375 p2.as_ref().set(5);
1376 assert_eq!(p1.as_ref().get(), 5);
1377 assert_eq!(p2.as_ref().get(), 5);
1378 assert_eq!(depends.as_ref().get(), 5 + 8);
1379 p1.as_ref().set(22);
1380 assert_eq!(p1.as_ref().get(), 22);
1381 assert_eq!(p2.as_ref().get(), 22);
1382 assert_eq!(depends.as_ref().get(), 22 + 8);
1383}
1384
1385#[test]
1386fn property_two_ways_test_binding() {
1387 let p1 = Rc::pin(Property::new(42));
1388 let p2 = Rc::pin(Property::new(88));
1389 let global = Rc::pin(Property::new(23));
1390 p2.as_ref().set_binding({
1391 let global = global.clone();
1392 move || global.as_ref().get() + 9
1393 });
1394
1395 let depends = Box::pin(Property::new(0));
1396 depends.as_ref().set_binding({
1397 let p1 = p1.clone();
1398 move || p1.as_ref().get() + 8
1399 });
1400
1401 Property::link_two_way(p1.as_ref(), p2.as_ref());
1402 assert_eq!(p1.as_ref().get(), 23 + 9);
1403 assert_eq!(p2.as_ref().get(), 23 + 9);
1404 assert_eq!(depends.as_ref().get(), 23 + 9 + 8);
1405 global.as_ref().set(55);
1406 assert_eq!(p1.as_ref().get(), 55 + 9);
1407 assert_eq!(p2.as_ref().get(), 55 + 9);
1408 assert_eq!(depends.as_ref().get(), 55 + 9 + 8);
1409}
1410
1411#[test]
1412fn property_two_ways_recurse_from_binding() {
1413 let xx = Rc::pin(Property::new(0));
1414
1415 let p1 = Rc::pin(Property::new(42));
1416 let p2 = Rc::pin(Property::new(88));
1417 let global = Rc::pin(Property::new(23));
1418
1419 let done = Rc::new(Cell::new(false));
1420 xx.set_binding({
1421 let p1 = p1.clone();
1422 let p2 = p2.clone();
1423 let global = global.clone();
1424 let xx_weak = pin_weak::rc::PinWeak::downgrade(xx.clone());
1425 move || {
1426 if !done.get() {
1427 done.set(true);
1428 Property::link_two_way(p1.as_ref(), p2.as_ref());
1429 let xx_weak = xx_weak.clone();
1430 p1.as_ref().set_binding(move || xx_weak.upgrade().unwrap().as_ref().get() + 9);
1431 }
1432 global.as_ref().get() + 2
1433 }
1434 });
1435 assert_eq!(xx.as_ref().get(), 23 + 2);
1436 assert_eq!(p1.as_ref().get(), 23 + 2 + 9);
1437 assert_eq!(p2.as_ref().get(), 23 + 2 + 9);
1438
1439 global.as_ref().set(55);
1440 assert_eq!(p1.as_ref().get(), 55 + 2 + 9);
1441 assert_eq!(p2.as_ref().get(), 55 + 2 + 9);
1442 assert_eq!(xx.as_ref().get(), 55 + 2);
1443}
1444
1445#[test]
1446fn property_two_ways_binding_of_two_way_binding_first() {
1447 let p1_1 = Rc::pin(Property::new(2));
1448 let p1_2 = Rc::pin(Property::new(4));
1449 Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1450
1451 assert_eq!(p1_1.as_ref().get(), 4);
1452 assert_eq!(p1_2.as_ref().get(), 4);
1453
1454 let p2 = Rc::pin(Property::new(3));
1455 Property::link_two_way(p1_1.as_ref(), p2.as_ref());
1456
1457 assert_eq!(p1_1.as_ref().get(), 3);
1458 assert_eq!(p1_2.as_ref().get(), 3);
1459 assert_eq!(p2.as_ref().get(), 3);
1460
1461 p1_1.set(6);
1462
1463 assert_eq!(p1_1.as_ref().get(), 6);
1464 assert_eq!(p1_2.as_ref().get(), 6);
1465 assert_eq!(p2.as_ref().get(), 6);
1466
1467 p1_2.set(8);
1468
1469 assert_eq!(p1_1.as_ref().get(), 8);
1470 assert_eq!(p1_2.as_ref().get(), 8);
1471 assert_eq!(p2.as_ref().get(), 8);
1472
1473 p2.set(7);
1474
1475 assert_eq!(p1_1.as_ref().get(), 7);
1476 assert_eq!(p1_2.as_ref().get(), 7);
1477 assert_eq!(p2.as_ref().get(), 7);
1478}
1479
1480#[test]
1481fn property_two_ways_binding_of_two_way_binding_second() {
1482 let p1 = Rc::pin(Property::new(2));
1483 let p2_1 = Rc::pin(Property::new(3));
1484 let p2_2 = Rc::pin(Property::new(5));
1485 Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1486
1487 assert_eq!(p2_1.as_ref().get(), 5);
1488 assert_eq!(p2_2.as_ref().get(), 5);
1489
1490 Property::link_two_way(p1.as_ref(), p2_2.as_ref());
1491
1492 assert_eq!(p1.as_ref().get(), 5);
1493 assert_eq!(p2_1.as_ref().get(), 5);
1494 assert_eq!(p2_2.as_ref().get(), 5);
1495
1496 p1.set(6);
1497
1498 assert_eq!(p1.as_ref().get(), 6);
1499 assert_eq!(p2_1.as_ref().get(), 6);
1500 assert_eq!(p2_2.as_ref().get(), 6);
1501
1502 p2_1.set(7);
1503
1504 assert_eq!(p1.as_ref().get(), 7);
1505 assert_eq!(p2_1.as_ref().get(), 7);
1506 assert_eq!(p2_2.as_ref().get(), 7);
1507
1508 p2_2.set(9);
1509
1510 assert_eq!(p1.as_ref().get(), 9);
1511 assert_eq!(p2_1.as_ref().get(), 9);
1512 assert_eq!(p2_2.as_ref().get(), 9);
1513}
1514
1515#[test]
1516fn property_two_ways_binding_of_two_two_way_bindings() {
1517 let p1_1 = Rc::pin(Property::new(2));
1518 let p1_2 = Rc::pin(Property::new(4));
1519 Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1520 assert_eq!(p1_1.as_ref().get(), 4);
1521 assert_eq!(p1_2.as_ref().get(), 4);
1522
1523 let p2_1 = Rc::pin(Property::new(3));
1524 let p2_2 = Rc::pin(Property::new(5));
1525 Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1526
1527 assert_eq!(p2_1.as_ref().get(), 5);
1528 assert_eq!(p2_2.as_ref().get(), 5);
1529
1530 Property::link_two_way(p1_1.as_ref(), p2_2.as_ref());
1531
1532 assert_eq!(p1_1.as_ref().get(), 5);
1533 assert_eq!(p1_2.as_ref().get(), 5);
1534 assert_eq!(p2_1.as_ref().get(), 5);
1535 assert_eq!(p2_2.as_ref().get(), 5);
1536
1537 p1_1.set(6);
1538 assert_eq!(p1_1.as_ref().get(), 6);
1539 assert_eq!(p1_2.as_ref().get(), 6);
1540 assert_eq!(p2_1.as_ref().get(), 6);
1541 assert_eq!(p2_2.as_ref().get(), 6);
1542
1543 p1_2.set(8);
1544 assert_eq!(p1_1.as_ref().get(), 8);
1545 assert_eq!(p1_2.as_ref().get(), 8);
1546 assert_eq!(p2_1.as_ref().get(), 8);
1547 assert_eq!(p2_2.as_ref().get(), 8);
1548
1549 p2_1.set(7);
1550 assert_eq!(p1_1.as_ref().get(), 7);
1551 assert_eq!(p1_2.as_ref().get(), 7);
1552 assert_eq!(p2_1.as_ref().get(), 7);
1553 assert_eq!(p2_2.as_ref().get(), 7);
1554
1555 p2_2.set(9);
1556 assert_eq!(p1_1.as_ref().get(), 9);
1557 assert_eq!(p1_2.as_ref().get(), 9);
1558 assert_eq!(p2_1.as_ref().get(), 9);
1559 assert_eq!(p2_2.as_ref().get(), 9);
1560}
1561
1562#[test]
1563fn test_two_way_with_map() {
1564 #[derive(PartialEq, Clone, Default, Debug)]
1565 struct Struct {
1566 foo: i32,
1567 bar: alloc::string::String,
1568 }
1569 let p1 = Rc::pin(Property::new(Struct { foo: 42, bar: "hello".into() }));
1570 let p2 = Rc::pin(Property::new(88));
1571 let p3 = Rc::pin(Property::new(alloc::string::String::from("xyz")));
1572 Property::link_two_way_with_map(p1.as_ref(), p2.as_ref(), |s| s.foo, |s, foo| s.foo = *foo);
1573 assert_eq!(p1.as_ref().get(), Struct { foo: 42, bar: "hello".into() });
1574 assert_eq!(p2.as_ref().get(), 42);
1575
1576 p2.as_ref().set(81);
1577 assert_eq!(p1.as_ref().get(), Struct { foo: 81, bar: "hello".into() });
1578 assert_eq!(p2.as_ref().get(), 81);
1579
1580 p1.as_ref().set(Struct { foo: 78, bar: "world".into() });
1581 assert_eq!(p1.as_ref().get(), Struct { foo: 78, bar: "world".into() });
1582 assert_eq!(p2.as_ref().get(), 78);
1583
1584 Property::link_two_way_with_map(
1585 p1.as_ref(),
1586 p3.as_ref(),
1587 |s| s.bar.clone(),
1588 |s, bar| s.bar = bar.clone(),
1589 );
1590 assert_eq!(p1.as_ref().get(), Struct { foo: 78, bar: "world".into() });
1591 assert_eq!(p2.as_ref().get(), 78);
1592 assert_eq!(p3.as_ref().get(), "world");
1593
1594 p3.as_ref().set("abc".into());
1595 assert_eq!(p1.as_ref().get(), Struct { foo: 78, bar: "abc".into() });
1596 assert_eq!(p2.as_ref().get(), 78);
1597 assert_eq!(p3.as_ref().get(), "abc");
1598
1599 let p4 = Rc::pin(Property::new(123));
1600 p2.set_binding({
1601 let p4 = p4.clone();
1602 move || p4.as_ref().get() + 1
1603 });
1604
1605 assert_eq!(p1.as_ref().get(), Struct { foo: 124, bar: "abc".into() });
1606 assert_eq!(p2.as_ref().get(), 124);
1607 assert_eq!(p3.as_ref().get(), "abc");
1608
1609 p4.as_ref().set(456);
1610 assert_eq!(p1.as_ref().get(), Struct { foo: 457, bar: "abc".into() });
1611 assert_eq!(p2.as_ref().get(), 457);
1612 assert_eq!(p3.as_ref().get(), "abc");
1613
1614 p3.as_ref().set("def".into());
1615 assert_eq!(p1.as_ref().get(), Struct { foo: 457, bar: "def".into() });
1616 assert_eq!(p2.as_ref().get(), 457);
1617 assert_eq!(p3.as_ref().get(), "def");
1618
1619 p4.as_ref().set(789);
1620 assert_eq!(p1.as_ref().get(), Struct { foo: 457, bar: "def".into() });
1622 assert_eq!(p2.as_ref().get(), 457);
1623 assert_eq!(p3.as_ref().get(), "def");
1624}
1625
1626mod change_tracker;
1627pub use change_tracker::*;
1628mod properties_animations;
1629pub use crate::items::StateInfo;
1630pub use properties_animations::*;
1631
1632struct StateInfoBinding<F> {
1633 dirty_time: Cell<Option<crate::animations::Instant>>,
1634 binding: F,
1635}
1636
1637unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable<StateInfo> for StateInfoBinding<F> {
1638 fn evaluate(self: Pin<&Self>, value: &mut StateInfo) -> BindingResult {
1639 let new_state = (self.binding)();
1640 let timestamp = self.dirty_time.take();
1641 if new_state != value.current_state {
1642 value.previous_state = value.current_state;
1643 value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1644 value.current_state = new_state;
1645 }
1646 BindingResult::KeepBinding
1647 }
1648
1649 fn mark_dirty(self: Pin<&Self>) {
1650 if self.dirty_time.get().is_none() {
1651 self.dirty_time.set(Some(crate::animations::current_tick()))
1652 }
1653 }
1654}
1655
1656pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
1658 let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding };
1659 unsafe {
1661 property.handle.set_binding(
1662 bind_callable,
1663 #[cfg(slint_debug_property)]
1664 property.debug_name.borrow().as_str(),
1665 )
1666 }
1667}
1668
1669#[doc(hidden)]
1670pub trait PropertyDirtyHandler {
1671 fn notify(self: Pin<&Self>);
1672}
1673
1674impl PropertyDirtyHandler for () {
1675 fn notify(self: Pin<&Self>) {}
1676}
1677
1678impl<F: Fn()> PropertyDirtyHandler for F {
1679 fn notify(self: Pin<&Self>) {
1680 (self.get_ref())()
1681 }
1682}
1683
1684pub struct PropertyTracker<DirtyHandler = ()> {
1687 holder: BindingHolder<DirtyHandler>,
1688}
1689
1690impl Default for PropertyTracker<()> {
1691 fn default() -> Self {
1692 static VT: &BindingVTable = &BindingVTable {
1693 drop: |_| (),
1694 evaluate: |_, _| BindingResult::KeepBinding,
1695 mark_dirty: |_, _| (),
1696 intercept_set: |_, _| false,
1697 intercept_set_binding: |_, _| false,
1698 };
1699
1700 let holder = BindingHolder {
1701 dependencies: Cell::new(0),
1702 dep_nodes: Default::default(),
1703 vtable: VT,
1704 dirty: Cell::new(true), is_two_way_binding: false,
1706 pinned: PhantomPinned,
1707 binding: (),
1708 #[cfg(slint_debug_property)]
1709 debug_name: "<PropertyTracker<()>>".into(),
1710 };
1711 Self { holder }
1712 }
1713}
1714
1715impl<DirtyHandler> Drop for PropertyTracker<DirtyHandler> {
1716 fn drop(&mut self) {
1717 unsafe {
1718 DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1719 }
1720 }
1721}
1722
1723impl<DirtyHandler: PropertyDirtyHandler> PropertyTracker<DirtyHandler> {
1724 #[cfg(slint_debug_property)]
1725 pub fn set_debug_name(&mut self, debug_name: alloc::string::String) {
1727 self.holder.debug_name = debug_name;
1728 }
1729
1730 pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1732 if CURRENT_BINDING.is_set() {
1733 CURRENT_BINDING.with(|cur_binding| {
1734 if let Some(cur_binding) = cur_binding {
1735 debug_assert!(!core::ptr::eq(
1736 self.holder.dependencies.get() as *const u32,
1737 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
1738 ));
1739 cur_binding.register_self_as_dependency(
1740 self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1741 #[cfg(slint_debug_property)]
1742 &self.holder.debug_name,
1743 );
1744 }
1745 });
1746 }
1747 }
1748
1749 pub fn is_dirty(&self) -> bool {
1752 self.holder.dirty.get()
1753 }
1754
1755 pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1759 self.register_as_dependency_to_current_binding();
1760 self.evaluate_as_dependency_root(f)
1761 }
1762
1763 pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1767 self.holder.dep_nodes.set(Default::default());
1769
1770 let pinned_holder = unsafe {
1772 self.map_unchecked(|s| {
1773 core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder)
1774 })
1775 };
1776 let r = CURRENT_BINDING.set(Some(pinned_holder), f);
1777 self.holder.dirty.set(false);
1778 r
1779 }
1780
1781 pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1784 self.register_as_dependency_to_current_binding();
1785 self.is_dirty().then(|| self.evaluate_as_dependency_root(f))
1786 }
1787
1788 pub fn set_dirty(&self) {
1790 self.holder.dirty.set(true);
1791 unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1792 }
1793
1794 pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1805 unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1807 _self: *const BindingHolder,
1808 was_dirty: bool,
1809 ) {
1810 if !was_dirty {
1811 unsafe {
1812 Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify()
1813 };
1814 }
1815 }
1816
1817 trait HasBindingVTable {
1818 const VT: &'static BindingVTable;
1819 }
1820 impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1821 const VT: &'static BindingVTable = &BindingVTable {
1822 drop: |_| (),
1823 evaluate: |_, _| BindingResult::KeepBinding,
1824 mark_dirty: mark_dirty::<B>,
1825 intercept_set: |_, _| false,
1826 intercept_set_binding: |_, _| false,
1827 };
1828 }
1829
1830 let holder = BindingHolder {
1831 dependencies: Cell::new(0),
1832 dep_nodes: Default::default(),
1833 vtable: <DirtyHandler as HasBindingVTable>::VT,
1834 dirty: Cell::new(true), is_two_way_binding: false,
1836 pinned: PhantomPinned,
1837 binding: handler,
1838 #[cfg(slint_debug_property)]
1839 debug_name: "<PropertyTracker>".into(),
1840 };
1841 Self { holder }
1842 }
1843}
1844
1845#[test]
1846fn test_property_handler_binding() {
1847 assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED), false);
1848 assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_POINTER_TO_BINDING), false);
1849 assert_eq!(
1850 PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED | BINDING_POINTER_TO_BINDING),
1851 false
1852 );
1853 assert_eq!(PropertyHandle::has_no_binding_or_lock(0), true);
1854}
1855
1856#[test]
1857fn test_property_listener_scope() {
1858 let scope = Box::pin(PropertyTracker::default());
1859 let prop1 = Box::pin(Property::new(42));
1860 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1863 assert_eq!(r, 42);
1864 assert!(!scope.is_dirty()); prop1.as_ref().set(88);
1866 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1868 assert_eq!(r, 89);
1869 assert!(!scope.is_dirty());
1870 let r = scope.as_ref().evaluate(|| 12);
1871 assert_eq!(r, 12);
1872 assert!(!scope.is_dirty());
1873 prop1.as_ref().set(1);
1874 assert!(!scope.is_dirty());
1875 scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1876 scope.set_dirty();
1877 let mut ok = false;
1878 scope.as_ref().evaluate_if_dirty(|| ok = true);
1879 assert!(ok);
1880}
1881
1882#[test]
1883fn test_nested_property_trackers() {
1884 let tracker1 = Box::pin(PropertyTracker::default());
1885 let tracker2 = Box::pin(PropertyTracker::default());
1886 let prop = Box::pin(Property::new(42));
1887
1888 let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1889 assert_eq!(r, 42);
1890
1891 prop.as_ref().set(1);
1892 assert!(tracker2.as_ref().is_dirty());
1893 assert!(tracker1.as_ref().is_dirty());
1894
1895 let r = tracker1
1896 .as_ref()
1897 .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1898 assert_eq!(r, 1);
1899 prop.as_ref().set(100);
1900 assert!(tracker2.as_ref().is_dirty());
1901 assert!(!tracker1.as_ref().is_dirty());
1902}
1903
1904#[test]
1905fn test_property_dirty_handler() {
1906 let call_flag = Rc::new(Cell::new(false));
1907 let tracker = Box::pin(PropertyTracker::new_with_dirty_handler({
1908 let call_flag = call_flag.clone();
1909 move || {
1910 (*call_flag).set(true);
1911 }
1912 }));
1913 let prop = Box::pin(Property::new(42));
1914
1915 let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1916
1917 assert_eq!(r, 42);
1918 assert!(!tracker.as_ref().is_dirty());
1919 assert!(!call_flag.get());
1920
1921 prop.as_ref().set(100);
1922 assert!(tracker.as_ref().is_dirty());
1923 assert!(call_flag.get());
1924
1925 call_flag.set(false);
1928 prop.as_ref().set(101);
1929 assert!(tracker.as_ref().is_dirty());
1930 assert!(!call_flag.get());
1931}
1932
1933#[test]
1934fn test_property_tracker_drop() {
1935 let outer_tracker = Box::pin(PropertyTracker::default());
1936 let inner_tracker = Box::pin(PropertyTracker::default());
1937 let prop = Box::pin(Property::new(42));
1938
1939 let r =
1940 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1941 assert_eq!(r, 42);
1942
1943 drop(inner_tracker);
1944 prop.as_ref().set(200); }
1946
1947#[test]
1948fn test_nested_property_tracker_dirty() {
1949 let outer_tracker = Box::pin(PropertyTracker::default());
1950 let inner_tracker = Box::pin(PropertyTracker::default());
1951 let prop = Box::pin(Property::new(42));
1952
1953 let r =
1954 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1955 assert_eq!(r, 42);
1956
1957 assert!(!outer_tracker.is_dirty());
1958 assert!(!inner_tracker.is_dirty());
1959
1960 inner_tracker.as_ref().set_dirty();
1963 assert!(outer_tracker.is_dirty());
1964}
1965
1966#[test]
1967#[allow(clippy::redundant_closure)]
1968fn test_nested_property_tracker_evaluate_if_dirty() {
1969 let outer_tracker = Box::pin(PropertyTracker::default());
1970 let inner_tracker = Box::pin(PropertyTracker::default());
1971 let prop = Box::pin(Property::new(42));
1972
1973 let mut cache = 0;
1974 let mut cache_or_evaluate = || {
1975 if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1976 cache = x;
1977 }
1978 cache
1979 };
1980 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1981 assert_eq!(r, 43);
1982 assert!(!outer_tracker.is_dirty());
1983 assert!(!inner_tracker.is_dirty());
1984 prop.as_ref().set(11);
1985 assert!(outer_tracker.is_dirty());
1986 assert!(inner_tracker.is_dirty());
1987 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1988 assert_eq!(r, 12);
1989}
1990
1991#[cfg(feature = "ffi")]
1992pub(crate) mod ffi;