1#![allow(unsafe_code)]
14#![warn(missing_docs)]
15
16mod single_linked_list_pin {
20 #![allow(unsafe_code)]
21 use core::pin::Pin;
22 use core::ptr::NonNull;
23
24 type NodePtr<T> = Option<NonNull<SingleLinkedListPinNode<T>>>;
25 struct SingleLinkedListPinNode<T> {
26 next: NodePtr<T>,
27 value: T,
28 }
29
30 pub struct SingleLinkedListPinHead<T>(NodePtr<T>);
31 impl<T> Default for SingleLinkedListPinHead<T> {
32 fn default() -> Self {
33 Self(None)
34 }
35 }
36
37 impl<T> Drop for SingleLinkedListPinHead<T> {
38 fn drop(&mut self) {
39 let mut cur = self.0.take();
41 while let Some(node) = cur {
42 unsafe {
45 cur = (*node.as_ptr()).next;
46 core::ptr::drop_in_place(&raw mut (*node.as_ptr()).value);
47 alloc::alloc::dealloc(
48 node.as_ptr().cast(),
49 core::alloc::Layout::new::<SingleLinkedListPinNode<T>>(),
50 );
51 }
52 }
53 }
54 }
55
56 impl<T> SingleLinkedListPinHead<T> {
57 pub fn push_front(&mut self, value: T) -> Pin<&T> {
58 let node = SingleLinkedListPinNode { next: self.0.take(), value };
59 let ptr = unsafe {
61 let layout = core::alloc::Layout::new::<SingleLinkedListPinNode<T>>();
62 let mem = alloc::alloc::alloc(layout) as *mut SingleLinkedListPinNode<T>;
63 assert!(!mem.is_null(), "allocation failed");
64 core::ptr::write(mem, node);
65 NonNull::new_unchecked(mem)
66 };
67 self.0 = Some(ptr);
68 unsafe { Pin::new_unchecked(&(*ptr.as_ptr()).value) }
70 }
71
72 #[allow(unused)]
73 pub fn iter(&self) -> impl Iterator<Item = Pin<&T>> {
74 struct I<'a, T>(&'a NodePtr<T>);
75
76 impl<'a, T> Iterator for I<'a, T> {
77 type Item = Pin<&'a T>;
78 fn next(&mut self) -> Option<Self::Item> {
79 if let Some(node) = self.0 {
80 let r = unsafe { Pin::new_unchecked(&(*node.as_ptr()).value) };
82 self.0 = unsafe { &(*node.as_ptr()).next };
83 Some(r)
84 } else {
85 None
86 }
87 }
88 }
89 I(&self.0)
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.0.is_none()
95 }
96 }
97
98 #[test]
99 fn test_list() {
100 let mut head = SingleLinkedListPinHead::default();
101 head.push_front(1);
102 head.push_front(2);
103 head.push_front(3);
104 assert_eq!(
105 head.iter().map(|x: Pin<&i32>| *x.get_ref()).collect::<std::vec::Vec<i32>>(),
106 std::vec![3, 2, 1]
107 );
108 }
109 #[test]
110 fn big_list() {
111 let mut head = SingleLinkedListPinHead::default();
113 for x in 0..100000 {
114 head.push_front(x);
115 }
116 }
117}
118
119pub(crate) mod dependency_tracker {
120 use core::cell::Cell;
126 use core::pin::Pin;
127
128 #[repr(transparent)]
129 pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>);
130
131 impl<T> Default for DependencyListHead<T> {
132 fn default() -> Self {
133 Self(Cell::new(core::ptr::null()))
134 }
135 }
136 impl<T> Drop for DependencyListHead<T> {
137 fn drop(&mut self) {
138 unsafe { DependencyListHead::drop(self as *mut Self) };
139 }
140 }
141
142 impl<T> DependencyListHead<T> {
143 pub unsafe fn mem_move(from: *mut Self, to: *mut Self) {
144 unsafe {
145 (*to).0.set((*from).0.get());
146 if let Some(next) = (*from).0.get().as_ref() {
147 debug_assert_eq!(from as *const _, next.prev.get() as *const _);
148 next.debug_assert_valid();
149 next.prev.set(to as *const _);
150 next.debug_assert_valid();
151 }
152 }
153 }
154
155 pub fn swap(from: Pin<&Self>, to: Pin<&Self>) {
157 Cell::swap(&from.0, &to.0);
158 unsafe {
159 if let Some(n) = from.0.get().as_ref() {
160 debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _);
161 n.prev.set(&from.0 as *const _);
162 n.debug_assert_valid();
163 }
164
165 if let Some(n) = to.0.get().as_ref() {
166 debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _);
167 n.prev.set(&to.0 as *const _);
168 n.debug_assert_valid();
169 }
170 }
171 }
172
173 pub fn is_empty(&self) -> bool {
175 self.0.get().is_null()
176 }
177
178 pub unsafe fn drop(_self: *mut Self) {
179 unsafe {
180 if let Some(next) = (*_self).0.get().as_ref() {
181 #[cfg(not(miri))]
182 debug_assert_eq!(_self as *const _, next.prev.get() as *const _);
183 next.debug_assert_valid();
184 next.prev.set(core::ptr::null());
185 next.debug_assert_valid();
186 }
187 }
188 }
189 pub fn append(&self, node: Pin<&DependencyNode<T>>) {
190 unsafe {
191 node.remove();
192 node.debug_assert_valid();
193 let old = self.0.get();
194 if let Some(x) = old.as_ref() {
195 x.debug_assert_valid();
196 }
197 self.0.set(node.get_ref() as *const DependencyNode<_>);
198 node.next.set(old);
199 node.prev.set(&self.0 as *const _);
200 if let Some(old) = old.as_ref() {
201 old.prev.set((&node.next) as *const _);
202 old.debug_assert_valid();
203 }
204 node.debug_assert_valid();
205 }
206 }
207
208 pub fn for_each(&self, mut f: impl FnMut(&T)) {
209 unsafe {
210 let mut next = self.0.get();
211 while let Some(node) = next.as_ref() {
212 node.debug_assert_valid();
213 next = node.next.get();
214 f(&node.binding);
215 }
216 }
217 }
218
219 pub fn take_head(&self) -> Option<T>
221 where
222 T: Copy,
223 {
224 unsafe {
225 if let Some(node) = self.0.get().as_ref() {
226 node.debug_assert_valid();
227 node.remove();
228 Some(node.binding)
229 } else {
230 None
231 }
232 }
233 }
234 }
235
236 pub struct DependencyNode<T> {
239 next: Cell<*const DependencyNode<T>>,
240 prev: Cell<*const Cell<*const DependencyNode<T>>>,
242 binding: T,
243 }
244
245 impl<T> DependencyNode<T> {
246 pub fn new(binding: T) -> Self {
247 Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding }
248 }
249
250 pub fn debug_assert_valid(&self) {
252 #[cfg(not(miri))]
255 unsafe {
256 debug_assert!(
257 self.prev.get().is_null() || core::ptr::eq((*self.prev.get()).get(), self)
258 );
259 debug_assert!(
260 self.next.get().is_null()
261 || core::ptr::eq((*self.next.get()).prev.get(), &self.next)
262 );
263 debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>);
265 debug_assert_ne!(
266 self.prev.get(),
267 (&self.next) as *const Cell<*const DependencyNode<T>>
268 );
269 }
270 }
271
272 pub fn remove(&self) {
273 self.debug_assert_valid();
274 unsafe {
275 if let Some(prev) = self.prev.get().as_ref() {
276 prev.set(self.next.get());
277 }
278 if let Some(next) = self.next.get().as_ref() {
279 next.debug_assert_valid();
280 next.prev.set(self.prev.get());
281 next.debug_assert_valid();
282 }
283 }
284 self.prev.set(core::ptr::null());
285 self.next.set(core::ptr::null());
286 }
287 }
288
289 impl<T> Drop for DependencyNode<T> {
290 fn drop(&mut self) {
291 self.remove();
292 }
293 }
294}
295
296type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>;
297type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>;
298
299use alloc::boxed::Box;
300use core::cell::{Cell, RefCell, UnsafeCell};
301use core::ffi::c_void;
302use core::marker::PhantomPinned;
303use core::pin::Pin;
304
305static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
308
309#[inline(always)]
310fn const_sentinel() -> *mut () {
311 (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as *mut ()
312}
313
314#[derive(Copy, Clone, Debug, Eq, PartialEq)]
316enum BindingResult {
317 KeepBinding,
319 RemoveBinding,
322}
323
324struct BindingVTable {
325 drop: unsafe fn(_self: *mut BindingHolder),
326 evaluate: unsafe fn(_self: *const BindingHolder, value: *mut c_void) -> BindingResult,
327 mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool),
328 intercept_set: unsafe fn(_self: *const BindingHolder, value: *const c_void) -> bool,
329 intercept_set_binding:
330 unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool,
331 velocity: unsafe fn(_self: *const BindingHolder) -> Option<f32>,
332}
333
334unsafe trait BindingCallable<T> {
340 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult;
343
344 fn mark_dirty(self: Pin<&Self>) {}
347
348 fn intercept_set(self: Pin<&Self>, _value: &T) -> bool {
354 false
355 }
356
357 unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
361 false
362 }
363
364 fn velocity(self: Pin<&Self>) -> Option<f32> {
367 None
368 }
369
370 const IS_TWO_WAY_BINDING: bool = false;
372}
373
374unsafe impl<T, F: Fn(&mut T) -> BindingResult> BindingCallable<T> for F {
375 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
376 self(value)
377 }
378}
379
380mod current_binding_storage {
382 use super::BindingHolder;
383 use core::cell::Cell;
384
385 #[cfg(feature = "std")]
386 std::thread_local! {
387 static CURRENT_BINDING: Cell<*const BindingHolder> = const { Cell::new(core::ptr::null()) };
388 }
389
390 #[cfg(feature = "std")]
391 pub(super) fn set<T>(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T {
392 CURRENT_BINDING.with(|cell| {
393 let old = cell.replace(value.unwrap_or(core::ptr::null()));
394 let res = f();
395 cell.set(old);
396 res
397 })
398 }
399
400 #[cfg(feature = "std")]
401 pub(super) fn with<T>(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T {
402 CURRENT_BINDING.with(|cell| {
403 let ptr = cell.get();
404 f(if ptr.is_null() { None } else { Some(ptr) })
405 })
406 }
407
408 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
409 static CURRENT_BINDING: ScopedRawPtr = ScopedRawPtr(Cell::new(core::ptr::null()));
410
411 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
412 struct ScopedRawPtr(Cell<*const BindingHolder>);
413 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
415 unsafe impl Send for ScopedRawPtr {}
416 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
417 unsafe impl Sync for ScopedRawPtr {}
418
419 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
420 pub(super) fn set<T>(value: Option<*const BindingHolder>, f: impl FnOnce() -> T) -> T {
421 let old = CURRENT_BINDING.0.replace(value.unwrap_or(core::ptr::null()));
422 let res = f();
423 CURRENT_BINDING.0.set(old);
424 res
425 }
426
427 #[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
428 pub(super) fn with<T>(f: impl FnOnce(Option<*const BindingHolder>) -> T) -> T {
429 let ptr = CURRENT_BINDING.0.get();
430 f(if ptr.is_null() { None } else { Some(ptr) })
431 }
432}
433
434pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
436 current_binding_storage::set(None, f)
437}
438
439pub fn is_currently_tracking() -> bool {
442 current_binding_storage::with(|x| x.is_some())
443}
444
445#[repr(C)]
447struct BindingHolder<B = ()> {
448 dependencies: Cell<*mut ()>,
450 dep_nodes: UnsafeCell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
454 vtable: &'static BindingVTable,
455 dirty: Cell<bool>,
457 is_two_way_binding: bool,
459 pinned: PhantomPinned,
460 #[cfg(slint_debug_property)]
461 pub debug_name: alloc::string::String,
462
463 binding: B,
464}
465
466impl BindingHolder {
467 fn register_self_as_dependency(
469 self_ptr: *const BindingHolder,
470 property_that_will_notify: *mut DependencyListHead,
471 #[cfg(slint_debug_property)] _other_debug_name: &str,
472 ) {
473 let node = DependencyNode::new(self_ptr);
474 unsafe {
476 let dep_nodes = &mut *(*self_ptr).dep_nodes.get();
477 let node = dep_nodes.push_front(node);
478 DependencyListHead::append(&*property_that_will_notify, node);
479 }
480 }
481}
482
483fn alloc_binding_holder<T, B: BindingCallable<T> + 'static>(binding: B) -> *mut BindingHolder {
484 unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
486 unsafe {
487 drop(Box::from_raw(_self as *mut BindingHolder<B>));
488 }
489 }
490
491 unsafe fn evaluate<T, B: BindingCallable<T>>(
494 _self: *const BindingHolder,
495 value: *mut c_void,
496 ) -> BindingResult {
497 unsafe {
498 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
499 .evaluate(&mut *(value as *mut T))
500 }
501 }
502
503 unsafe fn mark_dirty<T, B: BindingCallable<T>>(_self: *const BindingHolder, _: bool) {
505 unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty() }
506 }
507
508 unsafe fn intercept_set<T, B: BindingCallable<T>>(
510 _self: *const BindingHolder,
511 value: *const c_void,
512 ) -> bool {
513 unsafe {
514 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
515 .intercept_set(&*(value as *const T))
516 }
517 }
518
519 unsafe fn intercept_set_binding<T, B: BindingCallable<T>>(
520 _self: *const BindingHolder,
521 new_binding: *mut BindingHolder,
522 ) -> bool {
523 unsafe {
524 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
525 .intercept_set_binding(new_binding)
526 }
527 }
528
529 unsafe fn velocity<T, B: BindingCallable<T>>(_self: *const BindingHolder) -> Option<f32> {
531 unsafe { Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).velocity() }
532 }
533
534 trait HasBindingVTable<T> {
535 const VT: &'static BindingVTable;
536 }
537 impl<T, B: BindingCallable<T>> HasBindingVTable<T> for B {
538 const VT: &'static BindingVTable = &BindingVTable {
539 drop: binding_drop::<B>,
540 evaluate: evaluate::<T, B>,
541 mark_dirty: mark_dirty::<T, B>,
542 intercept_set: intercept_set::<T, B>,
543 intercept_set_binding: intercept_set_binding::<T, B>,
544 velocity: velocity::<T, B>,
545 };
546 }
547
548 let holder: BindingHolder<B> = BindingHolder {
549 dependencies: Cell::new(core::ptr::null_mut()),
550 dep_nodes: Default::default(),
551 vtable: <B as HasBindingVTable<T>>::VT,
552 dirty: Cell::new(true), is_two_way_binding: B::IS_TWO_WAY_BINDING,
554 pinned: PhantomPinned,
555 #[cfg(slint_debug_property)]
556 debug_name: Default::default(),
557 binding,
558 };
559 Box::into_raw(Box::new(holder)) as *mut BindingHolder
560}
561
562#[repr(transparent)]
563#[derive(Default)]
564struct PropertyHandle {
565 handle: Cell<*mut ()>,
570}
571
572const BINDING_BORROWED: usize = 0b01;
573const BINDING_POINTER_TO_BINDING: usize = 0b10;
574const BINDING_POINTER_MASK: usize = !(BINDING_POINTER_TO_BINDING | BINDING_BORROWED);
575
576impl core::fmt::Debug for PropertyHandle {
577 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
578 let handle = self.handle.get();
579 write!(
580 f,
581 "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}",
582 handle.addr() & !0b11,
583 self.lock_flag(),
584 PropertyHandle::is_pointer_to_binding(handle)
585 )
586 }
587}
588
589impl PropertyHandle {
590 #[inline]
592 fn lock_flag(&self) -> bool {
593 self.handle.get().addr() & BINDING_BORROWED != 0
594 }
595 unsafe fn set_lock_flag(&self, set: bool) {
598 self.handle.set(if set {
599 self.handle.get().map_addr(|a| a | BINDING_BORROWED)
600 } else {
601 self.handle.get().map_addr(|a| a & !BINDING_BORROWED)
602 })
603 }
604
605 #[inline]
606 fn is_pointer_to_binding(handle: *mut ()) -> bool {
607 handle.addr() & BINDING_POINTER_TO_BINDING != 0
608 }
609
610 #[inline]
612 fn pointer_to_binding(handle: *mut ()) -> Option<*mut BindingHolder> {
613 if Self::is_pointer_to_binding(handle) {
614 Some(handle.map_addr(|a| a & BINDING_POINTER_MASK) as *mut BindingHolder)
615 } else {
616 None
617 }
618 }
619
620 #[inline]
623 fn has_no_binding_or_lock(handle: *mut ()) -> bool {
624 handle.addr() & (BINDING_BORROWED | BINDING_POINTER_TO_BINDING) == 0
625 }
626
627 fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
630 #[cfg(slint_debug_property)]
631 if self.lock_flag() {
632 unsafe {
633 let handle = self.handle.get();
634 if let Some(binding_pointer) = Self::pointer_to_binding(handle) {
635 let binding = &mut *(binding_pointer);
636 let debug_name = &binding.debug_name;
637 panic!("Recursion detected with property {debug_name}");
638 }
639 }
640 }
641 assert!(!self.lock_flag(), "Recursion detected");
642 unsafe {
643 self.set_lock_flag(true);
644 scopeguard::defer! { self.set_lock_flag(false); }
645 let handle = self.handle.get();
646 let binding =
647 Self::pointer_to_binding(handle).map(|pointer| Pin::new_unchecked(&mut *(pointer)));
648 f(binding)
649 }
650 }
651
652 fn detach_binding(&self) -> Option<*mut BindingHolder> {
658 let binding = Self::pointer_to_binding(self.handle.get())?;
659 unsafe {
660 let const_sentinel = const_sentinel();
661 if (*binding).dependencies.get() == const_sentinel {
662 self.handle.set(const_sentinel);
663 } else {
664 DependencyListHead::mem_move(
665 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
666 self.handle.as_ptr() as *mut DependencyListHead,
667 );
668 }
669 (*binding).dependencies.set(core::ptr::null_mut());
670 }
671 Some(binding)
672 }
673
674 fn current_velocity(&self) -> Option<f32> {
677 self.access(|b| {
678 b.and_then(|b| unsafe {
679 (b.vtable.velocity)(&*b as *const BindingHolder)
681 })
682 })
683 }
684
685 fn remove_binding(&self) {
686 assert!(!self.lock_flag(), "Recursion detected");
687
688 if let Some(binding) = self.detach_binding() {
689 unsafe {
690 ((*binding).vtable.drop)(binding);
691 }
692 }
693 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
694 }
695
696 unsafe fn set_binding<T, B: BindingCallable<T> + 'static>(
698 &self,
699 binding: B,
700 #[cfg(slint_debug_property)] debug_name: &str,
701 ) {
702 let binding = alloc_binding_holder::<T, B>(binding);
703 #[cfg(slint_debug_property)]
704 unsafe {
705 (*binding).debug_name = debug_name.into();
706 }
707 self.set_binding_impl(binding);
708 }
709
710 fn set_binding_impl(&self, binding: *mut BindingHolder) {
712 let previous_binding_intercepted = self.access(|b| {
713 b.is_some_and(|b| unsafe {
714 (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
716 })
717 });
718
719 if previous_binding_intercepted {
720 return;
721 }
722
723 self.remove_binding();
724 debug_assert!(Self::has_no_binding_or_lock(binding as *mut ()));
725 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
726 let const_sentinel = const_sentinel();
727 let is_constant = self.handle.get() == const_sentinel;
728 unsafe {
729 if is_constant {
730 (*binding).dependencies.set(const_sentinel);
731 } else {
732 DependencyListHead::mem_move(
733 self.handle.as_ptr() as *mut DependencyListHead,
734 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
735 );
736 }
737 }
738 self.handle.set((binding as *mut ()).map_addr(|a| a | BINDING_POINTER_TO_BINDING));
739 if !is_constant {
740 self.mark_dirty(
741 #[cfg(slint_debug_property)]
742 "",
743 );
744 }
745 }
746
747 fn dependencies(&self) -> *mut DependencyListHead {
748 assert!(!self.lock_flag(), "Recursion detected");
749 if Self::is_pointer_to_binding(self.handle.get()) {
750 self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
751 } else {
752 self.handle.as_ptr() as *mut DependencyListHead
753 }
754 }
755
756 unsafe fn update<T>(&self, value: *mut T) {
759 let binding_ptr = Self::pointer_to_binding(self.handle.get());
760
761 let remove = self.access(|binding| {
762 if let Some(binding) = binding
763 && binding.dirty.get()
764 {
765 let binding_ptr = unsafe { binding_ptr.unwrap_unchecked() };
767
768 unsafe { *(*binding_ptr).dep_nodes.get() = Default::default() };
770 let r = unsafe {
771 current_binding_storage::set(Some(binding_ptr), || {
772 ((*binding_ptr).vtable.evaluate)(binding_ptr, value as *mut c_void)
773 })
774 };
775 unsafe { (*binding_ptr).dirty.set(false) };
776 if r == BindingResult::RemoveBinding {
777 return true;
778 }
779 }
780 false
781 });
782 if remove {
783 self.remove_binding()
784 }
785 }
786
787 fn register_as_dependency_to_current_binding(
789 self: Pin<&Self>,
790 #[cfg(slint_debug_property)] debug_name: &str,
791 ) {
792 current_binding_storage::with(|cur_binding| {
793 if let Some(cur_binding) = cur_binding {
794 let dependencies = self.dependencies();
795 if unsafe { *(dependencies as *mut *mut ()) } != const_sentinel() {
796 BindingHolder::register_self_as_dependency(
797 cur_binding,
798 dependencies,
799 #[cfg(slint_debug_property)]
800 debug_name,
801 );
802 }
803 }
804 });
805 }
806
807 fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
808 #[cfg(not(slint_debug_property))]
809 let debug_name = "";
810 unsafe {
811 let dependencies = self.dependencies();
812 assert!(
813 *(dependencies as *mut *mut ()) != const_sentinel(),
814 "Constant property being changed {debug_name}"
815 );
816 mark_dependencies_dirty(dependencies)
817 };
818 }
819
820 fn set_constant(&self) {
821 unsafe {
822 let dependencies = self.dependencies();
823 let const_sentinel = const_sentinel();
824 if *(dependencies as *mut *mut ()) != const_sentinel {
825 DependencyListHead::drop(dependencies);
826 *(dependencies as *mut *mut ()) = const_sentinel;
827 }
828 }
829 }
830
831 fn is_constant(&self) -> bool {
832 let dependencies = self.dependencies();
833 unsafe { *(dependencies as *mut *mut ()) == const_sentinel() }
835 }
836}
837
838impl Drop for PropertyHandle {
839 fn drop(&mut self) {
840 self.remove_binding();
841 debug_assert!(Self::has_no_binding_or_lock(self.handle.get()));
842 if self.handle.get() != const_sentinel() {
843 unsafe {
844 DependencyListHead::drop(self.handle.as_ptr() as *mut _);
845 }
846 }
847 }
848}
849
850unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
852 unsafe {
853 debug_assert!(*(dependencies as *mut *mut ()) != const_sentinel());
854 DependencyListHead::for_each(&*dependencies, |binding| {
855 let binding: &BindingHolder = &**binding;
856 let was_dirty = binding.dirty.replace(true);
857 (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
858
859 assert!(
860 binding.dependencies.get() != const_sentinel(),
861 "Const property marked as dirty"
862 );
863
864 if !was_dirty {
865 mark_dependencies_dirty(binding.dependencies.as_ptr() as *mut DependencyListHead)
866 }
867 });
868 }
869}
870
871pub trait Binding<T> {
873 fn evaluate(&self, old_value: &T) -> T;
875}
876
877impl<T, F: Fn() -> T> Binding<T> for F {
878 fn evaluate(&self, _value: &T) -> T {
879 self()
880 }
881}
882
883#[repr(C)]
892pub struct Property<T> {
893 handle: PropertyHandle,
895 value: UnsafeCell<T>,
897 pinned: PhantomPinned,
898 #[cfg(slint_debug_property)]
902 pub debug_name: RefCell<alloc::string::String>,
903}
904
905impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
906 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
907 #[cfg(slint_debug_property)]
908 write!(f, "[{}]=", self.debug_name.borrow())?;
909 write!(
910 f,
911 "Property({:?}{})",
912 self.get_internal(),
913 if self.is_dirty() { " (dirty)" } else { "" }
914 )
915 }
916}
917
918impl<T: Default> Default for Property<T> {
919 fn default() -> Self {
920 Self {
921 handle: Default::default(),
922 value: Default::default(),
923 pinned: PhantomPinned,
924 #[cfg(slint_debug_property)]
925 debug_name: Default::default(),
926 }
927 }
928}
929
930impl<T: Clone> Property<T> {
931 pub fn new(value: T) -> Self {
933 Self {
934 handle: Default::default(),
935 value: UnsafeCell::new(value),
936 pinned: PhantomPinned,
937 #[cfg(slint_debug_property)]
938 debug_name: Default::default(),
939 }
940 }
941
942 pub fn new_named(value: T, _name: &'static str) -> Self {
944 Self {
945 handle: Default::default(),
946 value: UnsafeCell::new(value),
947 pinned: PhantomPinned,
948 #[cfg(slint_debug_property)]
949 debug_name: RefCell::new(_name.into()),
950 }
951 }
952
953 pub fn get(self: Pin<&Self>) -> T {
963 unsafe { self.handle.update(self.value.get()) };
964 let handle = unsafe { Pin::new_unchecked(&self.handle) };
965 handle.register_as_dependency_to_current_binding(
966 #[cfg(slint_debug_property)]
967 self.debug_name.borrow().as_str(),
968 );
969 self.get_internal()
970 }
971
972 pub fn get_untracked(self: Pin<&Self>) -> T {
994 unsafe { self.handle.update(self.value.get()) };
995 self.get_internal()
996 }
997
998 pub fn register_as_dependency(self: Pin<&Self>) {
1009 let handle = unsafe { Pin::new_unchecked(&self.handle) };
1010 handle.register_as_dependency_to_current_binding(
1011 #[cfg(slint_debug_property)]
1012 self.debug_name.borrow().as_str(),
1013 );
1014 }
1015
1016 pub fn get_internal(&self) -> T {
1018 self.handle.access(|_| {
1019 unsafe { (*self.value.get()).clone() }
1021 })
1022 }
1023
1024 pub fn set(&self, t: T)
1030 where
1031 T: PartialEq,
1032 {
1033 let previous_binding_intercepted = self.handle.access(|b| {
1034 b.is_some_and(|b| unsafe {
1035 (b.vtable.intercept_set)(
1037 &*b as *const BindingHolder,
1038 (&t as *const T).cast::<c_void>(),
1039 )
1040 })
1041 });
1042 if !previous_binding_intercepted {
1043 self.handle.remove_binding();
1044 }
1045
1046 let has_value_changed = self.handle.access(|_| unsafe {
1048 *self.value.get() != t && {
1049 *self.value.get() = t;
1050 true
1051 }
1052 });
1053 if has_value_changed {
1054 self.handle.mark_dirty(
1055 #[cfg(slint_debug_property)]
1056 self.debug_name.borrow().as_str(),
1057 );
1058 }
1059 }
1060
1061 pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
1088 unsafe {
1090 self.handle.set_binding(
1091 move |val: &mut T| {
1092 *val = binding.evaluate(val);
1093 BindingResult::KeepBinding
1094 },
1095 #[cfg(slint_debug_property)]
1096 self.debug_name.borrow().as_str(),
1097 )
1098 }
1099 self.handle.mark_dirty(
1100 #[cfg(slint_debug_property)]
1101 self.debug_name.borrow().as_str(),
1102 );
1103 }
1104
1105 pub fn has_binding(&self) -> bool {
1107 PropertyHandle::pointer_to_binding(self.handle.handle.get()).is_some()
1108 }
1109
1110 pub fn is_dirty(&self) -> bool {
1113 self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
1114 }
1115
1116 pub fn mark_dirty(&self) {
1119 self.handle.mark_dirty(
1120 #[cfg(slint_debug_property)]
1121 self.debug_name.borrow().as_str(),
1122 )
1123 }
1124
1125 pub fn set_constant(&self) {
1127 self.handle.set_constant();
1128 }
1129
1130 pub fn is_constant(&self) -> bool {
1132 self.handle.is_constant()
1133 }
1134}
1135
1136#[test]
1137fn properties_simple_test() {
1138 use pin_weak::rc::PinWeak;
1139 use std::rc::Rc;
1140 fn g(prop: &Property<i32>) -> i32 {
1141 unsafe { Pin::new_unchecked(prop).get() }
1142 }
1143
1144 #[derive(Default)]
1145 struct Component {
1146 width: Property<i32>,
1147 height: Property<i32>,
1148 area: Property<i32>,
1149 }
1150
1151 let compo = Rc::pin(Component::default());
1152 let w = PinWeak::downgrade(compo.clone());
1153 compo.area.set_binding(move || {
1154 let compo = w.upgrade().unwrap();
1155 g(&compo.width) * g(&compo.height)
1156 });
1157 compo.width.set(4);
1158 compo.height.set(8);
1159 assert_eq!(g(&compo.width), 4);
1160 assert_eq!(g(&compo.height), 8);
1161 assert_eq!(g(&compo.area), 4 * 8);
1162
1163 let w = PinWeak::downgrade(compo.clone());
1164 compo.width.set_binding(move || {
1165 let compo = w.upgrade().unwrap();
1166 g(&compo.height) * 2
1167 });
1168 assert_eq!(g(&compo.width), 8 * 2);
1169 assert_eq!(g(&compo.height), 8);
1170 assert_eq!(g(&compo.area), 8 * 8 * 2);
1171}
1172
1173mod change_tracker;
1174mod erased_bindings;
1175mod two_way_binding;
1176pub use change_tracker::*;
1177pub use erased_bindings::*;
1178mod properties_animations;
1179pub use properties_animations::*;
1180
1181#[derive(Copy, Clone, Debug, PartialEq, Default)]
1184#[repr(C)]
1185pub struct StateInfo {
1186 pub current_state: i32,
1188 pub previous_state: i32,
1190 pub change_time: crate::animations::Instant,
1192}
1193
1194struct StateInfoBinding<F, T> {
1195 dirty_time: Cell<Option<crate::animations::Instant>>,
1196 binding: F,
1197 _phantom: core::marker::PhantomData<fn() -> T>,
1198}
1199
1200unsafe impl<F: Fn() -> i32, T> crate::properties::BindingCallable<T> for StateInfoBinding<F, T>
1201where
1202 T: Default + From<StateInfo> + 'static,
1203 StateInfo: TryFrom<T>,
1204{
1205 fn evaluate(self: Pin<&Self>, value: &mut T) -> BindingResult {
1206 let new_state = (self.binding)();
1207 let timestamp = self.dirty_time.take();
1208 let mut state_info: StateInfo = core::mem::take(value).try_into().unwrap_or_default();
1211 if new_state != state_info.current_state {
1212 state_info.previous_state = state_info.current_state;
1213 state_info.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1214 state_info.current_state = new_state;
1215 }
1216 *value = T::from(state_info);
1217 BindingResult::KeepBinding
1218 }
1219
1220 fn mark_dirty(self: Pin<&Self>) {
1221 if self.dirty_time.get().is_none() {
1222 self.dirty_time.set(Some(crate::animations::current_tick()))
1223 }
1224 }
1225}
1226
1227pub fn set_state_binding<T>(property: Pin<&Property<T>>, binding: impl Fn() -> i32 + 'static)
1232where
1233 T: Default + From<StateInfo> + 'static,
1234 StateInfo: TryFrom<T>,
1235{
1236 let bind_callable = StateInfoBinding {
1237 dirty_time: Cell::new(None),
1238 binding,
1239 _phantom: core::marker::PhantomData,
1240 };
1241 unsafe {
1242 property.handle.set_binding(
1243 bind_callable,
1244 #[cfg(slint_debug_property)]
1245 property.debug_name.borrow().as_str(),
1246 )
1247 }
1248}
1249
1250#[doc(hidden)]
1251pub trait PropertyDirtyHandler {
1252 fn notify(self: Pin<&Self>);
1253}
1254
1255impl PropertyDirtyHandler for () {
1256 fn notify(self: Pin<&Self>) {}
1257}
1258
1259impl<F: Fn()> PropertyDirtyHandler for F {
1260 fn notify(self: Pin<&Self>) {
1261 (self.get_ref())()
1262 }
1263}
1264
1265pub struct PropertyTracker<const NEEDS_SET_DIRTY: bool = false, DirtyHandler = ()> {
1274 holder: BindingHolder<DirtyHandler>,
1275}
1276
1277impl<const NEEDS_SET_DIRTY: bool> Default for PropertyTracker<NEEDS_SET_DIRTY, ()> {
1278 fn default() -> Self {
1279 static VT: &BindingVTable = &BindingVTable {
1280 drop: |_| (),
1281 evaluate: |_, _| BindingResult::KeepBinding,
1282 mark_dirty: |_, _| (),
1283 intercept_set: |_, _| false,
1284 intercept_set_binding: |_, _| false,
1285 velocity: |_| None,
1286 };
1287
1288 let holder = BindingHolder {
1289 dependencies: Cell::new(core::ptr::null_mut()),
1290 dep_nodes: Default::default(),
1291 vtable: VT,
1292 dirty: Cell::new(true), is_two_way_binding: false,
1294 pinned: PhantomPinned,
1295 binding: (),
1296 #[cfg(slint_debug_property)]
1297 debug_name: "<PropertyTracker<()>>".into(),
1298 };
1299 Self { holder }
1300 }
1301}
1302
1303impl<const NEEDS_SET_DIRTY: bool, DirtyHandler> Drop
1304 for PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1305{
1306 fn drop(&mut self) {
1307 unsafe {
1308 DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1309 }
1310 }
1311}
1312
1313impl<const NEEDS_SET_DIRTY: bool, DirtyHandler: PropertyDirtyHandler>
1314 PropertyTracker<NEEDS_SET_DIRTY, DirtyHandler>
1315{
1316 #[cfg(slint_debug_property)]
1317 pub fn set_debug_name(&mut self, debug_name: alloc::string::String) {
1319 self.holder.debug_name = debug_name;
1320 }
1321
1322 pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1324 if !NEEDS_SET_DIRTY && unsafe { (*self.holder.dep_nodes.get()).is_empty() } {
1326 return;
1327 }
1328 current_binding_storage::with(|cur_binding| {
1329 if let Some(cur_binding) = cur_binding {
1330 debug_assert!(self.holder.dependencies.get() != const_sentinel());
1331 BindingHolder::register_self_as_dependency(
1332 cur_binding,
1333 self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1334 #[cfg(slint_debug_property)]
1335 &self.holder.debug_name,
1336 );
1337 }
1338 });
1339 }
1340
1341 pub fn is_dirty(&self) -> bool {
1344 self.holder.dirty.get()
1345 }
1346
1347 pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1351 let r = self.evaluate_as_dependency_root(f);
1352 self.register_as_dependency_to_current_binding();
1353 r
1354 }
1355
1356 pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1360 unsafe { *self.holder.dep_nodes.get() = Default::default() };
1362
1363 let holder_ptr = &raw const self.holder as *const BindingHolder;
1364 let r = current_binding_storage::set(Some(holder_ptr), f);
1365 self.holder.dirty.set(false);
1366 r
1367 }
1368
1369 pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1372 let r = self.is_dirty().then(|| self.evaluate_as_dependency_root(f));
1373 self.register_as_dependency_to_current_binding();
1374 r
1375 }
1376
1377 pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1388 unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1390 _self: *const BindingHolder,
1391 was_dirty: bool,
1392 ) {
1393 if !was_dirty {
1394 unsafe {
1395 Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify()
1396 };
1397 }
1398 }
1399
1400 trait HasBindingVTable {
1401 const VT: &'static BindingVTable;
1402 }
1403 impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1404 const VT: &'static BindingVTable = &BindingVTable {
1405 drop: |_| (),
1406 evaluate: |_, _| BindingResult::KeepBinding,
1407 mark_dirty: mark_dirty::<B>,
1408 intercept_set: |_, _| false,
1409 intercept_set_binding: |_, _| false,
1410 velocity: |_| None,
1411 };
1412 }
1413
1414 let holder = BindingHolder {
1415 dependencies: Cell::new(core::ptr::null_mut()),
1416 dep_nodes: Default::default(),
1417 vtable: <DirtyHandler as HasBindingVTable>::VT,
1418 dirty: Cell::new(true), is_two_way_binding: false,
1420 pinned: PhantomPinned,
1421 binding: handler,
1422 #[cfg(slint_debug_property)]
1423 debug_name: "<PropertyTracker>".into(),
1424 };
1425 Self { holder }
1426 }
1427}
1428
1429impl<DirtyHandler> PropertyTracker<true, DirtyHandler> {
1430 pub fn set_dirty(&self) {
1432 self.holder.dirty.set(true);
1433 unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1434 }
1435}
1436
1437#[test]
1438fn test_property_handler_binding() {
1439 use core::ptr::without_provenance_mut;
1440 assert_eq!(
1441 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_BORROWED)),
1442 false
1443 );
1444 assert_eq!(
1445 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(BINDING_POINTER_TO_BINDING)),
1446 false
1447 );
1448 assert_eq!(
1449 PropertyHandle::has_no_binding_or_lock(without_provenance_mut(
1450 BINDING_BORROWED | BINDING_POINTER_TO_BINDING
1451 )),
1452 false
1453 );
1454 assert_eq!(PropertyHandle::has_no_binding_or_lock(core::ptr::null_mut()), true);
1455}
1456
1457#[test]
1458fn test_property_listener_scope() {
1459 let scope = Box::pin(PropertyTracker::default());
1460 let prop1 = Box::pin(Property::new(42));
1461 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1464 assert_eq!(r, 42);
1465 assert!(!scope.is_dirty()); prop1.as_ref().set(88);
1467 assert!(scope.is_dirty()); let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1469 assert_eq!(r, 89);
1470 assert!(!scope.is_dirty());
1471 let r = scope.as_ref().evaluate(|| 12);
1472 assert_eq!(r, 12);
1473 assert!(!scope.is_dirty());
1474 prop1.as_ref().set(1);
1475 assert!(!scope.is_dirty());
1476 scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1477 scope.set_dirty();
1478 let mut ok = false;
1479 scope.as_ref().evaluate_if_dirty(|| ok = true);
1480 assert!(ok);
1481}
1482
1483#[test]
1484fn test_nested_property_trackers() {
1485 let tracker1 = Box::pin(<PropertyTracker>::default());
1486 let tracker2 = Box::pin(<PropertyTracker>::default());
1487 let prop = Box::pin(Property::new(42));
1488
1489 let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1490 assert_eq!(r, 42);
1491
1492 prop.as_ref().set(1);
1493 assert!(tracker2.as_ref().is_dirty());
1494 assert!(tracker1.as_ref().is_dirty());
1495
1496 let r = tracker1
1497 .as_ref()
1498 .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1499 assert_eq!(r, 1);
1500 prop.as_ref().set(100);
1501 assert!(tracker2.as_ref().is_dirty());
1502 assert!(!tracker1.as_ref().is_dirty());
1503}
1504
1505#[test]
1506fn test_property_dirty_handler() {
1507 let call_flag = std::rc::Rc::new(Cell::new(false));
1508 let tracker = Box::pin(PropertyTracker::<false, _>::new_with_dirty_handler({
1509 let call_flag = call_flag.clone();
1510 move || {
1511 (*call_flag).set(true);
1512 }
1513 }));
1514 let prop = Box::pin(Property::new(42));
1515
1516 let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1517
1518 assert_eq!(r, 42);
1519 assert!(!tracker.as_ref().is_dirty());
1520 assert!(!call_flag.get());
1521
1522 prop.as_ref().set(100);
1523 assert!(tracker.as_ref().is_dirty());
1524 assert!(call_flag.get());
1525
1526 call_flag.set(false);
1529 prop.as_ref().set(101);
1530 assert!(tracker.as_ref().is_dirty());
1531 assert!(!call_flag.get());
1532}
1533
1534#[test]
1535fn test_property_tracker_drop() {
1536 let outer_tracker = Box::pin(<PropertyTracker>::default());
1537 let inner_tracker = Box::pin(<PropertyTracker>::default());
1538 let prop = Box::pin(Property::new(42));
1539
1540 let r =
1541 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1542 assert_eq!(r, 42);
1543
1544 drop(inner_tracker);
1545 prop.as_ref().set(200); }
1547
1548#[test]
1549fn test_nested_property_tracker_dirty() {
1550 let outer_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1551 let inner_tracker = Box::pin(PropertyTracker::<true, ()>::default());
1552 let prop = Box::pin(Property::new(42));
1553
1554 let r =
1555 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1556 assert_eq!(r, 42);
1557
1558 assert!(!outer_tracker.is_dirty());
1559 assert!(!inner_tracker.is_dirty());
1560
1561 inner_tracker.as_ref().set_dirty();
1564 assert!(outer_tracker.is_dirty());
1565}
1566
1567#[test]
1568#[allow(clippy::redundant_closure)]
1569fn test_nested_property_tracker_evaluate_if_dirty() {
1570 let outer_tracker = Box::pin(<PropertyTracker>::default());
1571 let inner_tracker = Box::pin(<PropertyTracker>::default());
1572 let prop = Box::pin(Property::new(42));
1573
1574 let mut cache = 0;
1575 let mut cache_or_evaluate = || {
1576 if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1577 cache = x;
1578 }
1579 cache
1580 };
1581 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1582 assert_eq!(r, 43);
1583 assert!(!outer_tracker.is_dirty());
1584 assert!(!inner_tracker.is_dirty());
1585 prop.as_ref().set(11);
1586 assert!(outer_tracker.is_dirty());
1587 assert!(inner_tracker.is_dirty());
1588 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1589 assert_eq!(r, 12);
1590}
1591
1592#[cfg(feature = "ffi")]
1593pub(crate) mod ffi;