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 pub fn is_empty(&self) -> bool {
73 self.0.is_none()
74 }
75 }
76
77 #[test]
78 fn test_list() {
79 let mut head = SingleLinkedListPinHead::default();
80 head.push_front(1);
81 head.push_front(2);
82 head.push_front(3);
83 assert_eq!(
84 head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::<std::vec::Vec<i32>>(),
85 std::vec![3, 2, 1]
86 );
87 }
88 #[test]
89 fn big_list() {
90 let mut head = SingleLinkedListPinHead::default();
92 for x in 0..100000 {
93 head.push_front(x);
94 }
95 }
96}
97
98pub(crate) mod dependency_tracker {
99 use core::cell::Cell;
105 use core::pin::Pin;
106
107 #[repr(transparent)]
108 pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>);
109
110 impl<T> Default for DependencyListHead<T> {
111 fn default() -> Self {
112 Self(Cell::new(core::ptr::null()))
113 }
114 }
115 impl<T> Drop for DependencyListHead<T> {
116 fn drop(&mut self) {
117 unsafe { DependencyListHead::drop(self as *mut Self) };
118 }
119 }
120
121 impl<T> DependencyListHead<T> {
122 pub unsafe fn mem_move(from: *mut Self, to: *mut Self) {
123 unsafe {
124 (*to).0.set((*from).0.get());
125 if let Some(next) = (*from).0.get().as_ref() {
126 debug_assert_eq!(from as *const _, next.prev.get() as *const _);
127 next.debug_assert_valid();
128 next.prev.set(to as *const _);
129 next.debug_assert_valid();
130 }
131 }
132 }
133
134 pub fn swap(from: Pin<&Self>, to: Pin<&Self>) {
136 Cell::swap(&from.0, &to.0);
137 unsafe {
138 if let Some(n) = from.0.get().as_ref() {
139 debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _);
140 n.prev.set(&from.0 as *const _);
141 n.debug_assert_valid();
142 }
143
144 if let Some(n) = to.0.get().as_ref() {
145 debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _);
146 n.prev.set(&to.0 as *const _);
147 n.debug_assert_valid();
148 }
149 }
150 }
151
152 pub fn is_empty(&self) -> bool {
154 self.0.get().is_null()
155 }
156
157 pub unsafe fn drop(_self: *mut Self) {
158 unsafe {
159 if let Some(next) = (*_self).0.get().as_ref() {
160 debug_assert_eq!(_self as *const _, next.prev.get() as *const _);
161 next.debug_assert_valid();
162 next.prev.set(core::ptr::null());
163 next.debug_assert_valid();
164 }
165 }
166 }
167 pub fn append(&self, node: Pin<&DependencyNode<T>>) {
168 unsafe {
169 node.remove();
170 node.debug_assert_valid();
171 let old = self.0.get();
172 if let Some(x) = old.as_ref() {
173 x.debug_assert_valid();
174 }
175 self.0.set(node.get_ref() as *const DependencyNode<_>);
176 node.next.set(old);
177 node.prev.set(&self.0 as *const _);
178 if let Some(old) = old.as_ref() {
179 old.prev.set((&node.next) as *const _);
180 old.debug_assert_valid();
181 }
182 node.debug_assert_valid();
183 }
184 }
185
186 pub fn for_each(&self, mut f: impl FnMut(&T)) {
187 unsafe {
188 let mut next = self.0.get();
189 while let Some(node) = next.as_ref() {
190 node.debug_assert_valid();
191 next = node.next.get();
192 f(&node.binding);
193 }
194 }
195 }
196
197 pub fn take_head(&self) -> Option<T>
199 where
200 T: Copy,
201 {
202 unsafe {
203 if let Some(node) = self.0.get().as_ref() {
204 node.debug_assert_valid();
205 node.remove();
206 Some(node.binding)
207 } else {
208 None
209 }
210 }
211 }
212 }
213
214 pub struct DependencyNode<T> {
217 next: Cell<*const DependencyNode<T>>,
218 prev: Cell<*const Cell<*const DependencyNode<T>>>,
220 binding: T,
221 }
222
223 impl<T> DependencyNode<T> {
224 pub fn new(binding: T) -> Self {
225 Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding }
226 }
227
228 pub fn debug_assert_valid(&self) {
230 unsafe {
231 debug_assert!(
232 self.prev.get().is_null() || core::ptr::eq((*self.prev.get()).get(), self)
233 );
234 debug_assert!(
235 self.next.get().is_null()
236 || core::ptr::eq((*self.next.get()).prev.get(), &self.next)
237 );
238 debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>);
240 debug_assert_ne!(
241 self.prev.get(),
242 (&self.next) as *const Cell<*const DependencyNode<T>>
243 );
244 }
245 }
246
247 pub fn remove(&self) {
248 self.debug_assert_valid();
249 unsafe {
250 if let Some(prev) = self.prev.get().as_ref() {
251 prev.set(self.next.get());
252 }
253 if let Some(next) = self.next.get().as_ref() {
254 next.debug_assert_valid();
255 next.prev.set(self.prev.get());
256 next.debug_assert_valid();
257 }
258 }
259 self.prev.set(core::ptr::null());
260 self.next.set(core::ptr::null());
261 }
262 }
263
264 impl<T> Drop for DependencyNode<T> {
265 fn drop(&mut self) {
266 self.remove();
267 }
268 }
269}
270
271type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>;
272type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>;
273
274use alloc::boxed::Box;
275use core::cell::{Cell, RefCell, UnsafeCell};
276use core::marker::PhantomPinned;
277use core::pin::Pin;
278
279static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
282
283#[derive(Copy, Clone, Debug, Eq, PartialEq)]
285enum BindingResult {
286 KeepBinding,
288 RemoveBinding,
291}
292
293struct BindingVTable {
294 drop: unsafe fn(_self: *mut BindingHolder),
295 evaluate: unsafe fn(_self: *const BindingHolder, value: *mut ()) -> BindingResult,
296 mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool),
297 intercept_set: unsafe fn(_self: *const BindingHolder, value: *const ()) -> bool,
298 intercept_set_binding:
299 unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool,
300}
301
302unsafe trait BindingCallable<T> {
308 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult;
311
312 fn mark_dirty(self: Pin<&Self>) {}
315
316 fn intercept_set(self: Pin<&Self>, _value: &T) -> bool {
322 false
323 }
324
325 unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
329 false
330 }
331
332 const IS_TWO_WAY_BINDING: bool = false;
334}
335
336unsafe impl<T, F: Fn(&mut T) -> BindingResult> BindingCallable<T> for F {
337 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
338 self(value)
339 }
340}
341
342#[cfg(feature = "std")]
343use std::thread_local;
344#[cfg(feature = "std")]
345scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Option<Pin<&'a BindingHolder>>);
346
347#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
348mod unsafe_single_threaded {
349 use super::BindingHolder;
350 use core::cell::Cell;
351 use core::pin::Pin;
352 use core::ptr::null;
353 pub(super) struct FakeThreadStorage(Cell<*const BindingHolder>);
354 impl FakeThreadStorage {
355 pub const fn new() -> Self {
356 Self(Cell::new(null()))
357 }
358 pub fn set<T>(&self, value: Option<Pin<&BindingHolder>>, f: impl FnOnce() -> T) -> T {
359 let old = self.0.replace(value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
360 let res = f();
361 let new = self.0.replace(old);
362 assert_eq!(new, value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
363 res
364 }
365 pub fn is_set(&self) -> bool {
366 !self.0.get().is_null()
367 }
368 pub fn with<T>(&self, f: impl FnOnce(Option<Pin<&BindingHolder>>) -> T) -> T {
369 let local = unsafe { self.0.get().as_ref().map(|x| Pin::new_unchecked(x)) };
370 let res = f(local);
371 assert_eq!(self.0.get(), local.map_or(null(), |v| v.get_ref() as *const BindingHolder));
372 res
373 }
374 }
375 unsafe impl Send for FakeThreadStorage {}
377 unsafe impl Sync for FakeThreadStorage {}
378}
379#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
380static CURRENT_BINDING: unsafe_single_threaded::FakeThreadStorage =
381 unsafe_single_threaded::FakeThreadStorage::new();
382
383pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
386 CURRENT_BINDING.set(None, f)
387}
388
389pub fn is_currently_tracking() -> bool {
392 CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x| x.is_some())
393}
394
395#[repr(C)]
397struct BindingHolder<B = ()> {
398 dependencies: Cell<usize>,
400 dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
403 vtable: &'static BindingVTable,
404 dirty: Cell<bool>,
406 is_two_way_binding: bool,
408 pinned: PhantomPinned,
409 #[cfg(slint_debug_property)]
410 pub debug_name: alloc::string::String,
411
412 binding: B,
413}
414
415impl BindingHolder {
416 fn register_self_as_dependency(
417 self: Pin<&Self>,
418 property_that_will_notify: *mut DependencyListHead,
419 #[cfg(slint_debug_property)] _other_debug_name: &str,
420 ) {
421 let node = DependencyNode::new(self.get_ref() as *const _);
422 let mut dep_nodes = self.dep_nodes.take();
423 let node = dep_nodes.push_front(node);
424 unsafe { DependencyListHead::append(&*property_that_will_notify, node) }
425 self.dep_nodes.set(dep_nodes);
426 }
427}
428
429fn alloc_binding_holder<T, B: BindingCallable<T> + 'static>(binding: B) -> *mut BindingHolder {
430 unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
432 unsafe {
433 drop(Box::from_raw(_self as *mut BindingHolder<B>));
434 }
435 }
436
437 unsafe fn evaluate<T, B: BindingCallable<T>>(
440 _self: *const BindingHolder,
441 value: *mut (),
442 ) -> BindingResult {
443 unsafe {
444 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
445 .evaluate(&mut *(value as *mut T))
446 }
447 }
448
449 unsafe fn mark_dirty<T, B: BindingCallable<T>>(_self: *const BindingHolder, _: bool) {
451 unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() }
452 }
453
454 unsafe fn intercept_set<T, B: BindingCallable<T>>(
456 _self: *const BindingHolder,
457 value: *const (),
458 ) -> bool {
459 unsafe {
460 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
461 .intercept_set(&*(value as *const T))
462 }
463 }
464
465 unsafe fn intercept_set_binding<T, B: BindingCallable<T>>(
466 _self: *const BindingHolder,
467 new_binding: *mut BindingHolder,
468 ) -> bool {
469 unsafe {
470 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
471 .intercept_set_binding(new_binding)
472 }
473 }
474
475 trait HasBindingVTable<T> {
476 const VT: &'static BindingVTable;
477 }
478 impl<T, B: BindingCallable<T>> HasBindingVTable<T> for B {
479 const VT: &'static BindingVTable = &BindingVTable {
480 drop: binding_drop::<B>,
481 evaluate: evaluate::<T, B>,
482 mark_dirty: mark_dirty::<T, B>,
483 intercept_set: intercept_set::<T, B>,
484 intercept_set_binding: intercept_set_binding::<T, B>,
485 };
486 }
487
488 let holder: BindingHolder<B> = BindingHolder {
489 dependencies: Cell::new(0),
490 dep_nodes: Default::default(),
491 vtable: <B as HasBindingVTable<T>>::VT,
492 dirty: Cell::new(true), is_two_way_binding: B::IS_TWO_WAY_BINDING,
494 pinned: PhantomPinned,
495 #[cfg(slint_debug_property)]
496 debug_name: Default::default(),
497 binding,
498 };
499 Box::into_raw(Box::new(holder)) as *mut BindingHolder
500}
501
502#[repr(transparent)]
503#[derive(Default)]
504struct PropertyHandle {
505 handle: Cell<usize>,
512}
513
514const BINDING_BORROWED: usize = 0b01;
515const BINDING_POINTER_TO_BINDING: usize = 0b10;
516const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED);
517
518impl core::fmt::Debug for PropertyHandle {
519 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
520 let handle = self.handle.get();
521 write!(
522 f,
523 "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}",
524 handle & !0b11,
525 self.lock_flag(),
526 PropertyHandle::is_pointer_to_binding(handle)
527 )
528 }
529}
530
531impl PropertyHandle {
532 #[inline]
534 fn lock_flag(&self) -> bool {
535 self.handle.get() & BINDING_BORROWED == BINDING_BORROWED
536 }
537 unsafe fn set_lock_flag(&self, set: bool) {
540 self.handle.set(if set {
541 self.handle.get() | BINDING_BORROWED
542 } else {
543 self.handle.get() & !BINDING_BORROWED
544 })
545 }
546
547 #[inline]
548 fn is_pointer_to_binding(handle: usize) -> bool {
549 handle & BINDING_POINTER_TO_BINDING == BINDING_POINTER_TO_BINDING
550 }
551
552 #[inline]
554 fn pointer_to_binding(handle: usize) -> Option<*mut BindingHolder> {
555 if Self::is_pointer_to_binding(handle) {
556 Some((handle & BINDING_POINTER_MASK) as *mut BindingHolder)
557 } else {
558 None
559 }
560 }
561
562 #[inline]
565 fn has_no_binding_or_lock(handle: usize) -> bool {
566 (handle as usize) & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0
567 }
568
569 fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
572 #[cfg(slint_debug_property)]
573 if self.lock_flag() {
574 unsafe {
575 let handle = self.handle.get();
576 if let Some(binding_pointer) = Self::pointer_to_binding(handle) {
577 let binding = &mut *(binding_pointer);
578 let debug_name = &binding.debug_name;
579 panic!("Recursion detected with property {debug_name}");
580 }
581 }
582 }
583 assert!(!self.lock_flag(), "Recursion detected");
584 unsafe {
585 self.set_lock_flag(true);
586 scopeguard::defer! { self.set_lock_flag(false); }
587 let handle = self.handle.get();
588 let binding =
589 Self::pointer_to_binding(handle).map(|pointer| Pin::new_unchecked(&mut *(pointer)));
590 f(binding)
591 }
592 }
593
594 fn detach_binding(&self) -> Option<*mut BindingHolder> {
600 let binding = Self::pointer_to_binding(self.handle.get())?;
601 unsafe {
602 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
603 if (*binding).dependencies.get() == const_sentinel {
604 self.handle.set(const_sentinel);
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).dependencies.set(0);
612 }
613 Some(binding)
614 }
615
616 fn remove_binding(&self) {
617 assert!(!self.lock_flag(), "Recursion detected");
618
619 if let Some(binding) = self.detach_binding() {
620 unsafe {
621 ((*binding).vtable.drop)(binding);
622 }
623 }
624 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
625 }
626
627 unsafe fn set_binding<T, B: BindingCallable<T> + 'static>(
629 &self,
630 binding: B,
631 #[cfg(slint_debug_property)] debug_name: &str,
632 ) {
633 let binding = alloc_binding_holder::<T, B>(binding);
634 #[cfg(slint_debug_property)]
635 unsafe {
636 (*binding).debug_name = debug_name.into();
637 }
638 self.set_binding_impl(binding);
639 }
640
641 fn set_binding_impl(&self, binding: *mut BindingHolder) {
643 let previous_binding_intercepted = self.access(|b| {
644 b.is_some_and(|b| unsafe {
645 (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
647 })
648 });
649
650 if previous_binding_intercepted {
651 return;
652 }
653
654 self.remove_binding();
655 debug_assert!(Self::has_no_binding_or_lock(binding as usize));
656 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
657 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
658 let is_constant = self.handle.get() == const_sentinel;
659 unsafe {
660 if is_constant {
661 (*binding).dependencies.set(const_sentinel);
662 } else {
663 DependencyListHead::mem_move(
664 self.handle.as_ptr() as *mut DependencyListHead,
665 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
666 );
667 }
668 }
669 self.handle.set((binding as usize) | BINDING_POINTER_TO_BINDING);
670 if !is_constant {
671 self.mark_dirty(
672 #[cfg(slint_debug_property)]
673 "",
674 );
675 }
676 }
677
678 fn dependencies(&self) -> *mut DependencyListHead {
679 assert!(!self.lock_flag(), "Recursion detected");
680 if Self::is_pointer_to_binding(self.handle.get()) {
681 self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
682 } else {
683 self.handle.as_ptr() as *mut DependencyListHead
684 }
685 }
686
687 unsafe fn update<T>(&self, value: *mut T) {
690 let remove = self.access(|binding| {
691 if let Some(binding) = binding
692 && binding.dirty.get()
693 {
694 unsafe fn evaluate_as_current_binding(
695 value: *mut (),
696 binding: Pin<&BindingHolder>,
697 ) -> BindingResult {
698 CURRENT_BINDING.set(Some(binding), || unsafe {
699 (binding.vtable.evaluate)(
700 binding.get_ref() as *const BindingHolder,
701 value as *mut (),
702 )
703 })
704 }
705
706 binding.dep_nodes.set(Default::default());
708 let r = unsafe { evaluate_as_current_binding(value as *mut (), binding.as_ref()) };
709 binding.dirty.set(false);
710 if r == BindingResult::RemoveBinding {
711 return true;
712 }
713 }
714 false
715 });
716 if remove {
717 self.remove_binding()
718 }
719 }
720
721 fn register_as_dependency_to_current_binding(
723 self: Pin<&Self>,
724 #[cfg(slint_debug_property)] debug_name: &str,
725 ) {
726 if CURRENT_BINDING.is_set() {
727 CURRENT_BINDING.with(|cur_binding| {
728 if let Some(cur_binding) = cur_binding {
729 let dependencies = self.dependencies();
730 if !core::ptr::eq(
731 unsafe { *(dependencies as *mut *const u32) },
732 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
733 ) {
734 cur_binding.register_self_as_dependency(
735 dependencies,
736 #[cfg(slint_debug_property)]
737 debug_name,
738 );
739 }
740 }
741 });
742 }
743 }
744
745 fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
746 #[cfg(not(slint_debug_property))]
747 let debug_name = "";
748 unsafe {
749 let dependencies = self.dependencies();
750 assert!(
751 !core::ptr::eq(
752 *(dependencies as *mut *const u32),
753 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
754 ),
755 "Constant property being changed {debug_name}"
756 );
757 mark_dependencies_dirty(dependencies)
758 };
759 }
760
761 fn set_constant(&self) {
762 unsafe {
763 let dependencies = self.dependencies();
764 if !core::ptr::eq(
765 *(dependencies as *mut *const u32),
766 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
767 ) {
768 DependencyListHead::drop(dependencies);
769 *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32
770 }
771 }
772 }
773
774 fn is_constant(&self) -> bool {
775 let dependencies = self.dependencies();
776 core::ptr::eq(
777 unsafe { *(dependencies as *mut *const u32) },
780 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
781 )
782 }
783}
784
785impl Drop for PropertyHandle {
786 fn drop(&mut self) {
787 self.remove_binding();
788 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
789 if !core::ptr::eq(self.handle.get() as *const u32, &CONSTANT_PROPERTY_SENTINEL) {
790 unsafe {
791 DependencyListHead::drop(self.handle.as_ptr() as *mut _);
792 }
793 }
794 }
795}
796
797unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
799 unsafe {
800 debug_assert!(!core::ptr::eq(
801 *(dependencies as *mut *const u32),
802 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
803 ));
804 DependencyListHead::for_each(&*dependencies, |binding| {
805 let binding: &BindingHolder = &**binding;
806 let was_dirty = binding.dirty.replace(true);
807 (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
808
809 assert!(
810 !core::ptr::eq(
811 *(binding.dependencies.as_ptr() as *mut *const u32),
812 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
813 ),
814 "Const property marked as dirty"
815 );
816
817 if !was_dirty {
818 mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead)
819 }
820 });
821 }
822}
823
824pub trait Binding<T> {
826 fn evaluate(&self, old_value: &T) -> T;
828}
829
830impl<T, F: Fn() -> T> Binding<T> for F {
831 fn evaluate(&self, _value: &T) -> T {
832 self()
833 }
834}
835
836#[repr(C)]
845pub struct Property<T> {
846 handle: PropertyHandle,
848 value: UnsafeCell<T>,
850 pinned: PhantomPinned,
851 #[cfg(slint_debug_property)]
855 pub debug_name: RefCell<alloc::string::String>,
856}
857
858impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
859 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
860 #[cfg(slint_debug_property)]
861 write!(f, "[{}]=", self.debug_name.borrow())?;
862 write!(
863 f,
864 "Property({:?}{})",
865 self.get_internal(),
866 if self.is_dirty() { " (dirty)" } else { "" }
867 )
868 }
869}
870
871impl<T: Default> Default for Property<T> {
872 fn default() -> Self {
873 Self {
874 handle: Default::default(),
875 value: Default::default(),
876 pinned: PhantomPinned,
877 #[cfg(slint_debug_property)]
878 debug_name: Default::default(),
879 }
880 }
881}
882
883impl<T: Clone> Property<T> {
884 pub fn new(value: T) -> Self {
886 Self {
887 handle: Default::default(),
888 value: UnsafeCell::new(value),
889 pinned: PhantomPinned,
890 #[cfg(slint_debug_property)]
891 debug_name: Default::default(),
892 }
893 }
894
895 pub fn new_named(value: T, _name: &'static str) -> Self {
897 Self {
898 handle: Default::default(),
899 value: UnsafeCell::new(value),
900 pinned: PhantomPinned,
901 #[cfg(slint_debug_property)]
902 debug_name: RefCell::new(_name.into()),
903 }
904 }
905
906 pub fn get(self: Pin<&Self>) -> T {
916 unsafe { self.handle.update(self.value.get()) };
917 let handle = unsafe { Pin::new_unchecked(&self.handle) };
918 handle.register_as_dependency_to_current_binding(
919 #[cfg(slint_debug_property)]
920 self.debug_name.borrow().as_str(),
921 );
922 self.get_internal()
923 }
924
925 pub fn get_untracked(self: Pin<&Self>) -> T {
947 unsafe { self.handle.update(self.value.get()) };
948 self.get_internal()
949 }
950
951 pub fn get_internal(&self) -> T {
953 self.handle.access(|_| {
954 unsafe { (*self.value.get()).clone() }
956 })
957 }
958
959 pub fn set(&self, t: T)
965 where
966 T: PartialEq,
967 {
968 let previous_binding_intercepted = self.handle.access(|b| {
969 b.is_some_and(|b| unsafe {
970 (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ())
972 })
973 });
974 if !previous_binding_intercepted {
975 self.handle.remove_binding();
976 }
977
978 let has_value_changed = self.handle.access(|_| unsafe {
980 *self.value.get() != t && {
981 *self.value.get() = t;
982 true
983 }
984 });
985 if has_value_changed {
986 self.handle.mark_dirty(
987 #[cfg(slint_debug_property)]
988 self.debug_name.borrow().as_str(),
989 );
990 }
991 }
992
993 pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
1020 unsafe {
1022 self.handle.set_binding(
1023 move |val: &mut T| {
1024 *val = binding.evaluate(val);
1025 BindingResult::KeepBinding
1026 },
1027 #[cfg(slint_debug_property)]
1028 self.debug_name.borrow().as_str(),
1029 )
1030 }
1031 self.handle.mark_dirty(
1032 #[cfg(slint_debug_property)]
1033 self.debug_name.borrow().as_str(),
1034 );
1035 }
1036
1037 pub fn has_binding(&self) -> bool {
1039 PropertyHandle::pointer_to_binding(self.handle.handle.get()).is_some()
1040 }
1041
1042 pub fn is_dirty(&self) -> bool {
1045 self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
1046 }
1047
1048 pub fn mark_dirty(&self) {
1051 self.handle.mark_dirty(
1052 #[cfg(slint_debug_property)]
1053 self.debug_name.borrow().as_str(),
1054 )
1055 }
1056
1057 pub fn set_constant(&self) {
1059 self.handle.set_constant();
1060 }
1061
1062 pub fn is_constant(&self) -> bool {
1064 self.handle.is_constant()
1065 }
1066}
1067
1068#[test]
1069fn properties_simple_test() {
1070 use pin_weak::rc::PinWeak;
1071 use std::rc::Rc;
1072 fn g(prop: &Property<i32>) -> i32 {
1073 unsafe { Pin::new_unchecked(prop).get() }
1074 }
1075
1076 #[derive(Default)]
1077 struct Component {
1078 width: Property<i32>,
1079 height: Property<i32>,
1080 area: Property<i32>,
1081 }
1082
1083 let compo = Rc::pin(Component::default());
1084 let w = PinWeak::downgrade(compo.clone());
1085 compo.area.set_binding(move || {
1086 let compo = w.upgrade().unwrap();
1087 g(&compo.width) * g(&compo.height)
1088 });
1089 compo.width.set(4);
1090 compo.height.set(8);
1091 assert_eq!(g(&compo.width), 4);
1092 assert_eq!(g(&compo.height), 8);
1093 assert_eq!(g(&compo.area), 4 * 8);
1094
1095 let w = PinWeak::downgrade(compo.clone());
1096 compo.width.set_binding(move || {
1097 let compo = w.upgrade().unwrap();
1098 g(&compo.height) * 2
1099 });
1100 assert_eq!(g(&compo.width), 8 * 2);
1101 assert_eq!(g(&compo.height), 8);
1102 assert_eq!(g(&compo.area), 8 * 8 * 2);
1103}
1104
1105mod change_tracker;
1106mod two_way_binding;
1107pub use change_tracker::*;
1108mod properties_animations;
1109pub use properties_animations::*;
1110
1111#[derive(Copy, Clone, Debug, PartialEq, Default)]
1114#[repr(C)]
1115pub struct StateInfo {
1116 pub current_state: i32,
1118 pub previous_state: i32,
1120 pub change_time: crate::animations::Instant,
1122}
1123
1124struct StateInfoBinding<F> {
1125 dirty_time: Cell<Option<crate::animations::Instant>>,
1126 binding: F,
1127}
1128
1129unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable<StateInfo> for StateInfoBinding<F> {
1130 fn evaluate(self: Pin<&Self>, value: &mut StateInfo) -> BindingResult {
1131 let new_state = (self.binding)();
1132 let timestamp = self.dirty_time.take();
1133 if new_state != value.current_state {
1134 value.previous_state = value.current_state;
1135 value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1136 value.current_state = new_state;
1137 }
1138 BindingResult::KeepBinding
1139 }
1140
1141 fn mark_dirty(self: Pin<&Self>) {
1142 if self.dirty_time.get().is_none() {
1143 self.dirty_time.set(Some(crate::animations::current_tick()))
1144 }
1145 }
1146}
1147
1148pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
1150 let bind_callable = StateInfoBinding { dirty_time: Cell::new(None), binding };
1151 unsafe {
1153 property.handle.set_binding(
1154 bind_callable,
1155 #[cfg(slint_debug_property)]
1156 property.debug_name.borrow().as_str(),
1157 )
1158 }
1159}
1160
1161#[doc(hidden)]
1162pub trait PropertyDirtyHandler {
1163 fn notify(self: Pin<&Self>);
1164}
1165
1166impl PropertyDirtyHandler for () {
1167 fn notify(self: Pin<&Self>) {}
1168}
1169
1170impl<F: Fn()> PropertyDirtyHandler for F {
1171 fn notify(self: Pin<&Self>) {
1172 (self.get_ref())()
1173 }
1174}
1175
1176pub struct PropertyTracker<const NEEDS_SET_DIRTY: bool = false, DirtyHandler = ()> {
1185 holder: BindingHolder<DirtyHandler>,
1186}
1187
1188impl<const NEEDS_SET_DIRTY: bool> Default for PropertyTracker<NEEDS_SET_DIRTY, ()> {
1189 fn default() -> Self {
1190 static VT: &BindingVTable = &BindingVTable {
1191 drop: |_| (),
1192 evaluate: |_, _| BindingResult::KeepBinding,
1193 mark_dirty: |_, _| (),
1194 intercept_set: |_, _| false,
1195 intercept_set_binding: |_, _| false,
1196 };
1197
1198 let holder = BindingHolder {
1199 dependencies: Cell::new(0),
1200 dep_nodes: Default::default(),
1201 vtable: VT,
1202 dirty: Cell::new(true), is_two_way_binding: false,
1204 pinned: PhantomPinned,
1205 binding: (),
1206 #[cfg(slint_debug_property)]
1207 debug_name: "<PropertyTracker<()>>".into(),
1208 };
1209 Self { holder }
1210 }
1211}
1212
1213impl<const NEEDS_SET_DIRTY: bool, DirtyHandler> Drop
1214 for PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1215{
1216 fn drop(&mut self) {
1217 unsafe {
1218 DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1219 }
1220 }
1221}
1222
1223impl<const NEEDS_SET_DIRTY: bool, DirtyHandler: PropertyDirtyHandler>
1224 PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1225{
1226 #[cfg(slint_debug_property)]
1227 pub fn set_debug_name(&mut self, debug_name: alloc::string::String) {
1229 self.holder.debug_name = debug_name;
1230 }
1231
1232 pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1234 let dep_nodes = self.holder.dep_nodes.take();
1235 if !NEEDS_SET_DIRTY && dep_nodes.is_empty() {
1236 return;
1238 }
1239 self.holder.dep_nodes.set(dep_nodes);
1240 if CURRENT_BINDING.is_set() {
1241 CURRENT_BINDING.with(|cur_binding| {
1242 if let Some(cur_binding) = cur_binding {
1243 debug_assert!(!core::ptr::eq(
1244 self.holder.dependencies.get() as *const u32,
1245 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
1246 ));
1247 cur_binding.register_self_as_dependency(
1248 self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1249 #[cfg(slint_debug_property)]
1250 &self.holder.debug_name,
1251 );
1252 }
1253 });
1254 }
1255 }
1256
1257 pub fn is_dirty(&self) -> bool {
1260 self.holder.dirty.get()
1261 }
1262
1263 pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1267 let r = self.evaluate_as_dependency_root(f);
1268 self.register_as_dependency_to_current_binding();
1269 r
1270 }
1271
1272 pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1276 self.holder.dep_nodes.set(Default::default());
1278
1279 let pinned_holder = unsafe {
1281 self.map_unchecked(|s| {
1282 core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder)
1283 })
1284 };
1285 let r = CURRENT_BINDING.set(Some(pinned_holder), f);
1286 self.holder.dirty.set(false);
1287 r
1288 }
1289
1290 pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1293 let r = self.is_dirty().then(|| self.evaluate_as_dependency_root(f));
1294 self.register_as_dependency_to_current_binding();
1295 r
1296 }
1297
1298 pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1309 unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1311 _self: *const BindingHolder,
1312 was_dirty: bool,
1313 ) {
1314 if !was_dirty {
1315 unsafe {
1316 Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify()
1317 };
1318 }
1319 }
1320
1321 trait HasBindingVTable {
1322 const VT: &'static BindingVTable;
1323 }
1324 impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1325 const VT: &'static BindingVTable = &BindingVTable {
1326 drop: |_| (),
1327 evaluate: |_, _| BindingResult::KeepBinding,
1328 mark_dirty: mark_dirty::<B>,
1329 intercept_set: |_, _| false,
1330 intercept_set_binding: |_, _| false,
1331 };
1332 }
1333
1334 let holder = BindingHolder {
1335 dependencies: Cell::new(0),
1336 dep_nodes: Default::default(),
1337 vtable: <DirtyHandler as HasBindingVTable>::VT,
1338 dirty: Cell::new(true), is_two_way_binding: false,
1340 pinned: PhantomPinned,
1341 binding: handler,
1342 #[cfg(slint_debug_property)]
1343 debug_name: "<PropertyTracker>".into(),
1344 };
1345 Self { holder }
1346 }
1347}
1348
1349impl<DirtyHandler> PropertyTracker<true, DirtyHandler> {
1350 pub fn set_dirty(&self) {
1352 self.holder.dirty.set(true);
1353 unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1354 }
1355}
1356
1357#[test]
1358fn test_property_handler_binding() {
1359 assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED), false);
1360 assert_eq!(PropertyHandle::has_no_binding_or_lock(BINDING_POINTER_TO_BINDING), false);
1361 assert_eq!(
1362 PropertyHandle::has_no_binding_or_lock(BINDING_BORROWED | BINDING_POINTER_TO_BINDING),
1363 false
1364 );
1365 assert_eq!(PropertyHandle::has_no_binding_or_lock(0), true);
1366}
1367
1368#[test]
1369fn test_property_listener_scope() {
1370 let scope = Box::pin(PropertyTracker::default());
1371 let prop1 = Box::pin(Property::new(42));
1372 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1375 assert_eq!(r, 42);
1376 assert!(!scope.is_dirty()); prop1.as_ref().set(88);
1378 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1380 assert_eq!(r, 89);
1381 assert!(!scope.is_dirty());
1382 let r = scope.as_ref().evaluate(|| 12);
1383 assert_eq!(r, 12);
1384 assert!(!scope.is_dirty());
1385 prop1.as_ref().set(1);
1386 assert!(!scope.is_dirty());
1387 scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1388 scope.set_dirty();
1389 let mut ok = false;
1390 scope.as_ref().evaluate_if_dirty(|| ok = true);
1391 assert!(ok);
1392}
1393
1394#[test]
1395fn test_nested_property_trackers() {
1396 let tracker1 = Box::pin(<PropertyTracker>::default());
1397 let tracker2 = Box::pin(<PropertyTracker>::default());
1398 let prop = Box::pin(Property::new(42));
1399
1400 let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1401 assert_eq!(r, 42);
1402
1403 prop.as_ref().set(1);
1404 assert!(tracker2.as_ref().is_dirty());
1405 assert!(tracker1.as_ref().is_dirty());
1406
1407 let r = tracker1
1408 .as_ref()
1409 .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1410 assert_eq!(r, 1);
1411 prop.as_ref().set(100);
1412 assert!(tracker2.as_ref().is_dirty());
1413 assert!(!tracker1.as_ref().is_dirty());
1414}
1415
1416#[test]
1417fn test_property_dirty_handler() {
1418 let call_flag = std::rc::Rc::new(Cell::new(false));
1419 let tracker = Box::pin(PropertyTracker::<false, _>::new_with_dirty_handler({
1420 let call_flag = call_flag.clone();
1421 move || {
1422 (*call_flag).set(true);
1423 }
1424 }));
1425 let prop = Box::pin(Property::new(42));
1426
1427 let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1428
1429 assert_eq!(r, 42);
1430 assert!(!tracker.as_ref().is_dirty());
1431 assert!(!call_flag.get());
1432
1433 prop.as_ref().set(100);
1434 assert!(tracker.as_ref().is_dirty());
1435 assert!(call_flag.get());
1436
1437 call_flag.set(false);
1440 prop.as_ref().set(101);
1441 assert!(tracker.as_ref().is_dirty());
1442 assert!(!call_flag.get());
1443}
1444
1445#[test]
1446fn test_property_tracker_drop() {
1447 let outer_tracker = Box::pin(<PropertyTracker>::default());
1448 let inner_tracker = Box::pin(<PropertyTracker>::default());
1449 let prop = Box::pin(Property::new(42));
1450
1451 let r =
1452 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1453 assert_eq!(r, 42);
1454
1455 drop(inner_tracker);
1456 prop.as_ref().set(200); }
1458
1459#[test]
1460fn test_nested_property_tracker_dirty() {
1461 let outer_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1462 let inner_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1463 let prop = Box::pin(Property::new(42));
1464
1465 let r =
1466 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1467 assert_eq!(r, 42);
1468
1469 assert!(!outer_tracker.is_dirty());
1470 assert!(!inner_tracker.is_dirty());
1471
1472 inner_tracker.as_ref().set_dirty();
1475 assert!(outer_tracker.is_dirty());
1476}
1477
1478#[test]
1479#[allow(clippy::redundant_closure)]
1480fn test_nested_property_tracker_evaluate_if_dirty() {
1481 let outer_tracker = Box::pin(<PropertyTracker>::default());
1482 let inner_tracker = Box::pin(<PropertyTracker>::default());
1483 let prop = Box::pin(Property::new(42));
1484
1485 let mut cache = 0;
1486 let mut cache_or_evaluate = || {
1487 if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1488 cache = x;
1489 }
1490 cache
1491 };
1492 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1493 assert_eq!(r, 43);
1494 assert!(!outer_tracker.is_dirty());
1495 assert!(!inner_tracker.is_dirty());
1496 prop.as_ref().set(11);
1497 assert!(outer_tracker.is_dirty());
1498 assert!(inner_tracker.is_dirty());
1499 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1500 assert_eq!(r, 12);
1501}
1502
1503#[cfg(feature = "ffi")]
1504pub(crate) mod ffi;