1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5 Property binding engine.
6
7 The current implementation uses lots of heap allocation but that can be optimized later using
8 thin dst container, and intrusive linked list
9*/
10
11// cSpell: ignore rustflags
12
13#![allow(unsafe_code)]
14#![warn(missing_docs)]
15
16/// A singled linked list whose nodes are pinned
17mod 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 // Use a loop instead of relying on the Drop of NodePtr to avoid recursion
38 while let Some(mut x) = core::mem::take(&mut self.0) {
39 // Safety: we don't touch the `x.value` which is the one protected by the Pin
40 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 // Safety: we can project from SingleLinkedListPinNode
49 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 // should not stack overflow
86 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 //! This module contains an implementation of a double linked list that can be used
95 //! to track dependency, such that when a node is dropped, the nodes are automatically
96 //! removed from the list.
97 //! This is unsafe to use for various reason, so it is kept internal.
98
99 use core::cell::Cell;
100 use core::pin::Pin;
101
102 #[repr(transparent)]
103 pub struct DependencyListHead<T>(Cell<*const DependencyNode<T>>);
104
105 impl<T> Default for DependencyListHead<T> {
106 fn default() -> Self {
107 Self(Cell::new(core::ptr::null()))
108 }
109 }
110 impl<T> Drop for DependencyListHead<T> {
111 fn drop(&mut self) {
112 unsafe { DependencyListHead::drop(self as *mut Self) };
113 }
114 }
115
116 impl<T> DependencyListHead<T> {
117 pub unsafe fn mem_move(from: *mut Self, to: *mut Self) {
118 (*to).0.set((*from).0.get());
119 if let Some(next) = (*from).0.get().as_ref() {
120 debug_assert_eq!(from as *const _, next.prev.get() as *const _);
121 next.debug_assert_valid();
122 next.prev.set(to as *const _);
123 next.debug_assert_valid();
124 }
125 }
126
127 /// Swap two list head
128 pub fn swap(from: Pin<&Self>, to: Pin<&Self>) {
129 Cell::swap(&from.0, &to.0);
130 unsafe {
131 if let Some(n) = from.0.get().as_ref() {
132 debug_assert_eq!(n.prev.get() as *const _, &to.0 as *const _);
133 n.prev.set(&from.0 as *const _);
134 n.debug_assert_valid();
135 }
136
137 if let Some(n) = to.0.get().as_ref() {
138 debug_assert_eq!(n.prev.get() as *const _, &from.0 as *const _);
139 n.prev.set(&to.0 as *const _);
140 n.debug_assert_valid();
141 }
142 }
143 }
144
145 /// Return true is the list is empty
146 pub fn is_empty(&self) -> bool {
147 self.0.get().is_null()
148 }
149
150 /// Remove all the nodes from the list;
151 pub fn clear(self: Pin<&Self>) {
152 unsafe {
153 if let Some(n) = self.0.get().as_ref() {
154 n.debug_assert_valid();
155 n.prev.set(core::ptr::null());
156 }
157 }
158 self.0.set(core::ptr::null());
159 }
160
161 pub unsafe fn drop(_self: *mut Self) {
162 if let Some(next) = (*_self).0.get().as_ref() {
163 debug_assert_eq!(_self as *const _, next.prev.get() as *const _);
164 next.debug_assert_valid();
165 next.prev.set(core::ptr::null());
166 next.debug_assert_valid();
167 }
168 }
169 pub fn append(&self, node: Pin<&DependencyNode<T>>) {
170 unsafe {
171 node.remove();
172 node.debug_assert_valid();
173 let old = self.0.get();
174 if let Some(x) = old.as_ref() {
175 x.debug_assert_valid();
176 }
177 self.0.set(node.get_ref() as *const DependencyNode<_>);
178 node.next.set(old);
179 node.prev.set(&self.0 as *const _);
180 if let Some(old) = old.as_ref() {
181 old.prev.set((&node.next) as *const _);
182 old.debug_assert_valid();
183 }
184 node.debug_assert_valid();
185 }
186 }
187
188 pub fn for_each(&self, mut f: impl FnMut(&T)) {
189 unsafe {
190 let mut next = self.0.get();
191 while let Some(node) = next.as_ref() {
192 node.debug_assert_valid();
193 next = node.next.get();
194 f(&node.binding);
195 }
196 }
197 }
198 }
199
200 /// The node is owned by the binding; so the binding is always valid
201 /// The next and pref
202 pub struct DependencyNode<T> {
203 next: Cell<*const DependencyNode<T>>,
204 /// This is either null, or a pointer to a pointer to ourself
205 prev: Cell<*const Cell<*const DependencyNode<T>>>,
206 binding: T,
207 }
208
209 impl<T> DependencyNode<T> {
210 pub fn new(binding: T) -> Self {
211 Self { next: Cell::new(core::ptr::null()), prev: Cell::new(core::ptr::null()), binding }
212 }
213
214 /// Assert that the invariant of `next` and `prev` are met.
215 pub fn debug_assert_valid(&self) {
216 unsafe {
217 debug_assert!(
218 self.prev.get().is_null()
219 || (*self.prev.get()).get() == self as *const DependencyNode<T>
220 );
221 debug_assert!(
222 self.next.get().is_null()
223 || (*self.next.get()).prev.get()
224 == (&self.next) as *const Cell<*const DependencyNode<T>>
225 );
226 // infinite loop?
227 debug_assert_ne!(self.next.get(), self as *const DependencyNode<T>);
228 debug_assert_ne!(
229 self.prev.get(),
230 (&self.next) as *const Cell<*const DependencyNode<T>>
231 );
232 }
233 }
234
235 pub fn remove(&self) {
236 self.debug_assert_valid();
237 unsafe {
238 if let Some(prev) = self.prev.get().as_ref() {
239 prev.set(self.next.get());
240 }
241 if let Some(next) = self.next.get().as_ref() {
242 next.debug_assert_valid();
243 next.prev.set(self.prev.get());
244 next.debug_assert_valid();
245 }
246 }
247 self.prev.set(core::ptr::null());
248 self.next.set(core::ptr::null());
249 }
250 }
251
252 impl<T> Drop for DependencyNode<T> {
253 fn drop(&mut self) {
254 self.remove();
255 }
256 }
257}
258
259type DependencyListHead = dependency_tracker::DependencyListHead<*const BindingHolder>;
260type DependencyNode = dependency_tracker::DependencyNode<*const BindingHolder>;
261
262use alloc::boxed::Box;
263use alloc::rc::Rc;
264use core::cell::{Cell, RefCell, UnsafeCell};
265use core::marker::PhantomPinned;
266use core::pin::Pin;
267
268/// if a DependencyListHead points to that value, it is because the property is actually
269/// constant and cannot have dependencies
270static CONSTANT_PROPERTY_SENTINEL: u32 = 0;
271
272/// The return value of a binding
273#[derive(Copy, Clone, Debug, Eq, PartialEq)]
274enum BindingResult {
275 /// The binding is a normal binding, and we keep it to re-evaluate it once it is dirty
276 KeepBinding,
277 /// The value of the property is now constant after the binding was evaluated, so
278 /// the binding can be removed.
279 RemoveBinding,
280}
281
282struct BindingVTable {
283 drop: unsafe fn(_self: *mut BindingHolder),
284 evaluate: unsafe fn(_self: *mut BindingHolder, value: *mut ()) -> BindingResult,
285 mark_dirty: unsafe fn(_self: *const BindingHolder, was_dirty: bool),
286 intercept_set: unsafe fn(_self: *const BindingHolder, value: *const ()) -> bool,
287 intercept_set_binding:
288 unsafe fn(_self: *const BindingHolder, new_binding: *mut BindingHolder) -> bool,
289}
290
291/// A binding trait object can be used to dynamically produces values for a property.
292///
293/// # Safety
294///
295/// IS_TWO_WAY_BINDING cannot be true if Self is not a TwoWayBinding
296unsafe trait BindingCallable {
297 /// This function is called by the property to evaluate the binding and produce a new value. The
298 /// previous property value is provided in the value parameter.
299 unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult;
300
301 /// This function is used to notify the binding that one of the dependencies was changed
302 /// and therefore this binding may evaluate to a different value, too.
303 fn mark_dirty(self: Pin<&Self>) {}
304
305 /// Allow the binding to intercept what happens when the value is set.
306 /// The default implementation returns false, meaning the binding will simply be removed and
307 /// the property will get the new value.
308 /// When returning true, the call was intercepted and the binding will not be removed,
309 /// but the property will still have that value
310 unsafe fn intercept_set(self: Pin<&Self>, _value: *const ()) -> bool {
311 false
312 }
313
314 /// Allow the binding to intercept what happens when the value is set.
315 /// The default implementation returns false, meaning the binding will simply be removed.
316 /// When returning true, the call was intercepted and the binding will not be removed.
317 unsafe fn intercept_set_binding(self: Pin<&Self>, _new_binding: *mut BindingHolder) -> bool {
318 false
319 }
320
321 /// Set to true if and only if Self is a TwoWayBinding<T>
322 const IS_TWO_WAY_BINDING: bool = false;
323}
324
325unsafe impl<F: Fn(*mut ()) -> BindingResult> BindingCallable for F {
326 unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
327 self(value)
328 }
329}
330
331#[cfg(feature = "std")]
332use std::thread_local;
333#[cfg(feature = "std")]
334scoped_tls_hkt::scoped_thread_local!(static CURRENT_BINDING : for<'a> Option<Pin<&'a BindingHolder>>);
335
336#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
337mod unsafe_single_threaded {
338 use super::BindingHolder;
339 use core::cell::Cell;
340 use core::pin::Pin;
341 use core::ptr::null;
342 pub(super) struct FakeThreadStorage(Cell<*const BindingHolder>);
343 impl FakeThreadStorage {
344 pub const fn new() -> Self {
345 Self(Cell::new(null()))
346 }
347 pub fn set<T>(&self, value: Option<Pin<&BindingHolder>>, f: impl FnOnce() -> T) -> T {
348 let old = self.0.replace(value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
349 let res = f();
350 let new = self.0.replace(old);
351 assert_eq!(new, value.map_or(null(), |v| v.get_ref() as *const BindingHolder));
352 res
353 }
354 pub fn is_set(&self) -> bool {
355 !self.0.get().is_null()
356 }
357 pub fn with<T>(&self, f: impl FnOnce(Option<Pin<&BindingHolder>>) -> T) -> T {
358 let local = unsafe { self.0.get().as_ref().map(|x| Pin::new_unchecked(x)) };
359 let res = f(local);
360 assert_eq!(self.0.get(), local.map_or(null(), |v| v.get_ref() as *const BindingHolder));
361 res
362 }
363 }
364 // Safety: the unsafe_single_threaded feature means we will only be called from a single thread
365 unsafe impl Send for FakeThreadStorage {}
366 unsafe impl Sync for FakeThreadStorage {}
367}
368#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
369static CURRENT_BINDING: unsafe_single_threaded::FakeThreadStorage =
370 unsafe_single_threaded::FakeThreadStorage::new();
371
372/// Evaluate a function, but do not register any property dependencies if that function
373/// get the value of properties
374pub fn evaluate_no_tracking<T>(f: impl FnOnce() -> T) -> T {
375 CURRENT_BINDING.set(t:None, f)
376}
377
378/// Return true if there is currently a binding being evaluated so that access to
379/// properties register dependencies to that binding.
380pub fn is_currently_tracking() -> bool {
381 CURRENT_BINDING.is_set() && CURRENT_BINDING.with(|x: Option>| x.is_some())
382}
383
384/// This structure erase the `B` type with a vtable.
385#[repr(C)]
386struct BindingHolder<B = ()> {
387 /// Access to the list of binding which depends on this binding
388 dependencies: Cell<usize>,
389 /// The binding own the nodes used in the dependencies lists of the properties
390 /// From which we depend.
391 dep_nodes: Cell<single_linked_list_pin::SingleLinkedListPinHead<DependencyNode>>,
392 vtable: &'static BindingVTable,
393 /// The binding is dirty and need to be re_evaluated
394 dirty: Cell<bool>,
395 /// Specify that B is a `TwoWayBinding<T>`
396 is_two_way_binding: bool,
397 pinned: PhantomPinned,
398 #[cfg(slint_debug_property)]
399 pub debug_name: String,
400
401 binding: B,
402}
403
404impl BindingHolder {
405 fn register_self_as_dependency(
406 self: Pin<&Self>,
407 property_that_will_notify: *mut DependencyListHead,
408 #[cfg(slint_debug_property)] other_debug_name: &str,
409 ) {
410 let node: DependencyNode<*const BindingHolder> = DependencyNode::new(self.get_ref() as *const _);
411 let mut dep_nodes: SingleLinkedListPinHead> = self.dep_nodes.take();
412 let node: Pin<&DependencyNode<*const …>> = dep_nodes.push_front(node);
413 unsafe { DependencyListHead::append(&*property_that_will_notify, node) }
414 self.dep_nodes.set(val:dep_nodes);
415 }
416}
417
418fn alloc_binding_holder<B: BindingCallable + 'static>(binding: B) -> *mut BindingHolder {
419 /// Safety: _self must be a pointer that comes from a `Box<BindingHolder<B>>::into_raw()`
420 unsafe fn binding_drop<B>(_self: *mut BindingHolder) {
421 drop(Box::from_raw(_self as *mut BindingHolder<B>));
422 }
423
424 /// Safety: _self must be a pointer to a `BindingHolder<B>`
425 /// and value must be a pointer to T
426 unsafe fn evaluate<B: BindingCallable>(
427 _self: *mut BindingHolder,
428 value: *mut (),
429 ) -> BindingResult {
430 let pinned_holder = Pin::new_unchecked(&*_self);
431 CURRENT_BINDING.set(Some(pinned_holder), || {
432 Pin::new_unchecked(&((*(_self as *mut BindingHolder<B>)).binding)).evaluate(value)
433 })
434 }
435
436 /// Safety: _self must be a pointer to a `BindingHolder<B>`
437 unsafe fn mark_dirty<B: BindingCallable>(_self: *const BindingHolder, _: bool) {
438 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).mark_dirty()
439 }
440
441 /// Safety: _self must be a pointer to a `BindingHolder<B>`
442 unsafe fn intercept_set<B: BindingCallable>(
443 _self: *const BindingHolder,
444 value: *const (),
445 ) -> bool {
446 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding)).intercept_set(value)
447 }
448
449 unsafe fn intercept_set_binding<B: BindingCallable>(
450 _self: *const BindingHolder,
451 new_binding: *mut BindingHolder,
452 ) -> bool {
453 Pin::new_unchecked(&((*(_self as *const BindingHolder<B>)).binding))
454 .intercept_set_binding(new_binding)
455 }
456
457 trait HasBindingVTable {
458 const VT: &'static BindingVTable;
459 }
460 impl<B: BindingCallable> HasBindingVTable for B {
461 const VT: &'static BindingVTable = &BindingVTable {
462 drop: binding_drop::<B>,
463 evaluate: evaluate::<B>,
464 mark_dirty: mark_dirty::<B>,
465 intercept_set: intercept_set::<B>,
466 intercept_set_binding: intercept_set_binding::<B>,
467 };
468 }
469
470 let holder: BindingHolder<B> = BindingHolder {
471 dependencies: Cell::new(0),
472 dep_nodes: Default::default(),
473 vtable: <B as HasBindingVTable>::VT,
474 dirty: Cell::new(true), // starts dirty so it evaluates the property when used
475 is_two_way_binding: B::IS_TWO_WAY_BINDING,
476 pinned: PhantomPinned,
477 #[cfg(slint_debug_property)]
478 debug_name: Default::default(),
479 binding,
480 };
481 Box::into_raw(Box::new(holder)) as *mut BindingHolder
482}
483
484#[repr(transparent)]
485#[derive(Default)]
486struct PropertyHandle {
487 /// The handle can either be a pointer to a binding, or a pointer to the list of dependent properties.
488 /// The two least significant bit of the pointer are flags, as the pointer will be aligned.
489 /// The least significant bit (`0b01`) tells that the binding is borrowed. So no two reference to the
490 /// binding exist at the same time.
491 /// The second to last bit (`0b10`) tells that the pointer points to a binding. Otherwise, it is the head
492 /// node of the linked list of dependent binding
493 handle: Cell<usize>,
494}
495
496impl core::fmt::Debug for PropertyHandle {
497 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
498 let handle: usize = self.handle.get();
499 write!(
500 f,
501 "PropertyHandle {{ handle: 0x{:x}, locked: {}, binding: {} }}",
502 handle & !0b11,
503 (handle & 0b01) == 0b01,
504 (handle & 0b10) == 0b10
505 )
506 }
507}
508
509impl PropertyHandle {
510 /// The lock flag specify that we can get reference to the Cell or unsafe cell
511 fn lock_flag(&self) -> bool {
512 self.handle.get() & 0b1 == 1
513 }
514 /// Sets the lock_flag.
515 /// Safety: the lock flag must not be unset if there exist reference to what's inside the cell
516 unsafe fn set_lock_flag(&self, set: bool) {
517 self.handle.set(if set { self.handle.get() | 0b1 } else { self.handle.get() & !0b1 })
518 }
519
520 /// Access the value.
521 /// Panics if the function try to recursively access the value
522 fn access<R>(&self, f: impl FnOnce(Option<Pin<&mut BindingHolder>>) -> R) -> R {
523 assert!(!self.lock_flag(), "Recursion detected");
524 unsafe {
525 self.set_lock_flag(true);
526 scopeguard::defer! { self.set_lock_flag(false); }
527 let handle = self.handle.get();
528 let binding = if handle & 0b10 == 0b10 {
529 Some(Pin::new_unchecked(&mut *((handle & !0b11) as *mut BindingHolder)))
530 } else {
531 None
532 };
533 f(binding)
534 }
535 }
536
537 fn remove_binding(&self) {
538 assert!(!self.lock_flag(), "Recursion detected");
539 let val = self.handle.get();
540 if val & 0b10 == 0b10 {
541 unsafe {
542 self.set_lock_flag(true);
543 let binding = (val & !0b11) as *mut BindingHolder;
544 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
545 if (*binding).dependencies.get() == const_sentinel {
546 self.handle.set(const_sentinel);
547 (*binding).dependencies.set(0);
548 } else {
549 DependencyListHead::mem_move(
550 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
551 self.handle.as_ptr() as *mut DependencyListHead,
552 );
553 }
554 ((*binding).vtable.drop)(binding);
555 }
556 debug_assert!(self.handle.get() & 0b11 == 0);
557 }
558 }
559
560 /// Safety: the BindingCallable must be valid for the type of this property
561 unsafe fn set_binding<B: BindingCallable + 'static>(
562 &self,
563 binding: B,
564 #[cfg(slint_debug_property)] debug_name: &str,
565 ) {
566 let binding = alloc_binding_holder::<B>(binding);
567 #[cfg(slint_debug_property)]
568 {
569 (*binding).debug_name = debug_name.into();
570 }
571 self.set_binding_impl(binding);
572 }
573
574 /// Implementation of Self::set_binding.
575 fn set_binding_impl(&self, binding: *mut BindingHolder) {
576 let previous_binding_intercepted = self.access(|b| {
577 b.is_some_and(|b| unsafe {
578 // Safety: b is a BindingHolder<T>
579 (b.vtable.intercept_set_binding)(&*b as *const BindingHolder, binding)
580 })
581 });
582
583 if previous_binding_intercepted {
584 return;
585 }
586
587 self.remove_binding();
588 debug_assert!((binding as usize) & 0b11 == 0);
589 debug_assert!(self.handle.get() & 0b11 == 0);
590 let const_sentinel = (&CONSTANT_PROPERTY_SENTINEL) as *const u32 as usize;
591 let is_constant = self.handle.get() == const_sentinel;
592 unsafe {
593 if is_constant {
594 (*binding).dependencies.set(const_sentinel);
595 } else {
596 DependencyListHead::mem_move(
597 self.handle.as_ptr() as *mut DependencyListHead,
598 (*binding).dependencies.as_ptr() as *mut DependencyListHead,
599 );
600 }
601 }
602 self.handle.set((binding as usize) | 0b10);
603 if !is_constant {
604 self.mark_dirty(
605 #[cfg(slint_debug_property)]
606 "",
607 );
608 }
609 }
610
611 fn dependencies(&self) -> *mut DependencyListHead {
612 assert!(!self.lock_flag(), "Recursion detected");
613 if (self.handle.get() & 0b10) != 0 {
614 self.access(|binding| binding.unwrap().dependencies.as_ptr() as *mut DependencyListHead)
615 } else {
616 self.handle.as_ptr() as *mut DependencyListHead
617 }
618 }
619
620 // `value` is the content of the unsafe cell and will be only dereferenced if the
621 // handle is not locked. (Upholding the requirements of UnsafeCell)
622 unsafe fn update<T>(&self, value: *mut T) {
623 let remove = self.access(|binding| {
624 if let Some(mut binding) = binding {
625 if binding.dirty.get() {
626 // clear all the nodes so that we can start from scratch
627 binding.dep_nodes.set(Default::default());
628 let r = (binding.vtable.evaluate)(
629 binding.as_mut().get_unchecked_mut() as *mut BindingHolder,
630 value as *mut (),
631 );
632 binding.dirty.set(false);
633 if r == BindingResult::RemoveBinding {
634 return true;
635 }
636 }
637 }
638 false
639 });
640 if remove {
641 self.remove_binding()
642 }
643 }
644
645 /// Register this property as a dependency to the current binding being evaluated
646 fn register_as_dependency_to_current_binding(
647 self: Pin<&Self>,
648 #[cfg(slint_debug_property)] debug_name: &str,
649 ) {
650 if CURRENT_BINDING.is_set() {
651 CURRENT_BINDING.with(|cur_binding| {
652 if let Some(cur_binding) = cur_binding {
653 let dependencies = self.dependencies();
654 if !core::ptr::eq(
655 unsafe { *(dependencies as *mut *const u32) },
656 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
657 ) {
658 cur_binding.register_self_as_dependency(
659 dependencies,
660 #[cfg(slint_debug_property)]
661 debug_name,
662 );
663 }
664 }
665 });
666 }
667 }
668
669 fn mark_dirty(&self, #[cfg(slint_debug_property)] debug_name: &str) {
670 #[cfg(not(slint_debug_property))]
671 let debug_name = "";
672 unsafe {
673 let dependencies = self.dependencies();
674 assert!(
675 !core::ptr::eq(
676 *(dependencies as *mut *const u32),
677 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
678 ),
679 "Constant property being changed {debug_name}"
680 );
681 mark_dependencies_dirty(dependencies)
682 };
683 }
684
685 fn set_constant(&self) {
686 unsafe {
687 let dependencies = self.dependencies();
688 if !core::ptr::eq(
689 *(dependencies as *mut *const u32),
690 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
691 ) {
692 DependencyListHead::drop(dependencies);
693 *(dependencies as *mut *const u32) = (&CONSTANT_PROPERTY_SENTINEL) as *const u32
694 }
695 }
696 }
697}
698
699impl Drop for PropertyHandle {
700 fn drop(&mut self) {
701 self.remove_binding();
702 debug_assert!(self.handle.get() & 0b11 == 0);
703 if self.handle.get() as *const u32 != (&CONSTANT_PROPERTY_SENTINEL) as *const u32 {
704 unsafe {
705 DependencyListHead::drop(self.handle.as_ptr() as *mut _);
706 }
707 }
708 }
709}
710
711/// Safety: the dependency list must be valid and consistent
712unsafe fn mark_dependencies_dirty(dependencies: *mut DependencyListHead) {
713 debug_assert!(!core::ptr::eq(
714 *(dependencies as *mut *const u32),
715 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
716 ));
717 DependencyListHead::for_each(&*dependencies, |binding: &*const BindingHolder| {
718 let binding: &BindingHolder = &**binding;
719 let was_dirty: bool = binding.dirty.replace(val:true);
720 (binding.vtable.mark_dirty)(binding as *const BindingHolder, was_dirty);
721
722 assert!(
723 !core::ptr::eq(
724 *(binding.dependencies.as_ptr() as *mut *const u32),
725 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
726 ),
727 "Const property marked as dirty"
728 );
729
730 if !was_dirty {
731 mark_dependencies_dirty(dependencies:binding.dependencies.as_ptr() as *mut DependencyListHead)
732 }
733 });
734}
735
736/// Types that can be set as bindings for a `Property<T>`
737pub trait Binding<T> {
738 /// Evaluate the binding and return the new value
739 fn evaluate(&self, old_value: &T) -> T;
740}
741
742impl<T, F: Fn() -> T> Binding<T> for F {
743 fn evaluate(&self, _value: &T) -> T {
744 self()
745 }
746}
747
748/// A Property that allow binding that track changes
749///
750/// Property can have an assigned value, or binding.
751/// When a binding is assigned, it is lazily evaluated on demand
752/// when calling `get()`.
753/// When accessing another property from a binding evaluation,
754/// a dependency will be registered, such that when the property
755/// change, the binding will automatically be updated
756#[repr(C)]
757pub struct Property<T> {
758 /// This is usually a pointer, but the least significant bit tells what it is
759 handle: PropertyHandle,
760 /// This is only safe to access when the lock flag is not set on the handle.
761 value: UnsafeCell<T>,
762 pinned: PhantomPinned,
763 /// Enabled only if compiled with `RUSTFLAGS='--cfg slint_debug_property'`
764 /// Note that adding this flag will also tell the rust compiler to set this
765 /// and that this will not work with C++ because of binary incompatibility
766 #[cfg(slint_debug_property)]
767 pub debug_name: RefCell<String>,
768}
769
770impl<T: core::fmt::Debug + Clone> core::fmt::Debug for Property<T> {
771 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
772 #[cfg(slint_debug_property)]
773 write!(f, "[{}]=", self.debug_name.borrow())?;
774 write!(
775 f,
776 "Property({:?}{})",
777 self.get_internal(),
778 if self.is_dirty() { " (dirty)" } else { "" }
779 )
780 }
781}
782
783impl<T: Default> Default for Property<T> {
784 fn default() -> Self {
785 Self {
786 handle: Default::default(),
787 value: Default::default(),
788 pinned: PhantomPinned,
789 #[cfg(slint_debug_property)]
790 debug_name: Default::default(),
791 }
792 }
793}
794
795impl<T: Clone> Property<T> {
796 /// Create a new property with this value
797 pub fn new(value: T) -> Self {
798 Self {
799 handle: Default::default(),
800 value: UnsafeCell::new(value),
801 pinned: PhantomPinned,
802 #[cfg(slint_debug_property)]
803 debug_name: Default::default(),
804 }
805 }
806
807 /// Same as [`Self::new`] but with a 'static string use for debugging only
808 pub fn new_named(value: T, _name: &'static str) -> Self {
809 Self {
810 handle: Default::default(),
811 value: UnsafeCell::new(value),
812 pinned: PhantomPinned,
813 #[cfg(slint_debug_property)]
814 debug_name: _name.to_owned().into(),
815 }
816 }
817
818 /// Get the value of the property
819 ///
820 /// This may evaluate the binding if there is a binding and it is dirty
821 ///
822 /// If the function is called directly or indirectly from a binding evaluation
823 /// of another Property, a dependency will be registered.
824 ///
825 /// Panics if this property is get while evaluating its own binding or
826 /// cloning the value.
827 pub fn get(self: Pin<&Self>) -> T {
828 unsafe { self.handle.update(self.value.get()) };
829 let handle = unsafe { Pin::new_unchecked(&self.handle) };
830 handle.register_as_dependency_to_current_binding(
831 #[cfg(slint_debug_property)]
832 self.debug_name.borrow().as_str(),
833 );
834 self.get_internal()
835 }
836
837 /// Same as get() but without registering a dependency
838 ///
839 /// This allow to optimize bindings that know that they might not need to
840 /// re_evaluate themselves when the property change or that have registered
841 /// the dependency in another way.
842 ///
843 /// ## Example
844 /// ```
845 /// use std::rc::Rc;
846 /// use i_slint_core::Property;
847 /// let prop1 = Rc::pin(Property::new(100));
848 /// let prop2 = Rc::pin(Property::<i32>::default());
849 /// prop2.as_ref().set_binding({
850 /// let prop1 = prop1.clone(); // in order to move it into the closure.
851 /// move || { prop1.as_ref().get_untracked() + 30 }
852 /// });
853 /// assert_eq!(prop2.as_ref().get(), 130);
854 /// prop1.set(200);
855 /// // changing prop1 do not affect the prop2 binding because no dependency was registered
856 /// assert_eq!(prop2.as_ref().get(), 130);
857 /// ```
858 pub fn get_untracked(self: Pin<&Self>) -> T {
859 unsafe { self.handle.update(self.value.get()) };
860 self.get_internal()
861 }
862
863 /// Get the cached value without registering any dependencies or executing any binding
864 pub fn get_internal(&self) -> T {
865 self.handle.access(|_| {
866 // Safety: PropertyHandle::access ensure that the value is locked
867 unsafe { (*self.value.get()).clone() }
868 })
869 }
870
871 /// Change the value of this property
872 ///
873 /// If other properties have binding depending of this property, these properties will
874 /// be marked as dirty.
875 // FIXME pub fn set(self: Pin<&Self>, t: T) {
876 pub fn set(&self, t: T)
877 where
878 T: PartialEq,
879 {
880 let previous_binding_intercepted = self.handle.access(|b| {
881 b.is_some_and(|b| unsafe {
882 // Safety: b is a BindingHolder<T>
883 (b.vtable.intercept_set)(&*b as *const BindingHolder, &t as *const T as *const ())
884 })
885 });
886 if !previous_binding_intercepted {
887 self.handle.remove_binding();
888 }
889
890 // Safety: PropertyHandle::access ensure that the value is locked
891 let has_value_changed = self.handle.access(|_| unsafe {
892 *self.value.get() != t && {
893 *self.value.get() = t;
894 true
895 }
896 });
897 if has_value_changed {
898 self.handle.mark_dirty(
899 #[cfg(slint_debug_property)]
900 self.debug_name.borrow().as_str(),
901 );
902 }
903 }
904
905 /// Set a binding to this property.
906 ///
907 /// Bindings are evaluated lazily from calling get, and the return value of the binding
908 /// is the new value.
909 ///
910 /// If other properties have bindings depending of this property, these properties will
911 /// be marked as dirty.
912 ///
913 /// Closures of type `Fn()->T` implements `Binding<T>` and can be used as a binding
914 ///
915 /// ## Example
916 /// ```
917 /// use std::rc::Rc;
918 /// use i_slint_core::Property;
919 /// let prop1 = Rc::pin(Property::new(100));
920 /// let prop2 = Rc::pin(Property::<i32>::default());
921 /// prop2.as_ref().set_binding({
922 /// let prop1 = prop1.clone(); // in order to move it into the closure.
923 /// move || { prop1.as_ref().get() + 30 }
924 /// });
925 /// assert_eq!(prop2.as_ref().get(), 130);
926 /// prop1.set(200);
927 /// // A change in prop1 forced the binding on prop2 to re_evaluate
928 /// assert_eq!(prop2.as_ref().get(), 230);
929 /// ```
930 //FIXME pub fn set_binding(self: Pin<&Self>, f: impl Binding<T> + 'static) {
931 pub fn set_binding(&self, binding: impl Binding<T> + 'static) {
932 // Safety: This will make a binding callable for the type T
933 unsafe {
934 self.handle.set_binding(
935 move |val: *mut ()| {
936 let val = &mut *(val as *mut T);
937 *val = binding.evaluate(val);
938 BindingResult::KeepBinding
939 },
940 #[cfg(slint_debug_property)]
941 self.debug_name.borrow().as_str(),
942 )
943 }
944 self.handle.mark_dirty(
945 #[cfg(slint_debug_property)]
946 self.debug_name.borrow().as_str(),
947 );
948 }
949
950 /// Any of the properties accessed during the last evaluation of the closure called
951 /// from the last call to evaluate is potentially dirty.
952 pub fn is_dirty(&self) -> bool {
953 self.handle.access(|binding| binding.is_some_and(|b| b.dirty.get()))
954 }
955
956 /// Internal function to mark the property as dirty and notify dependencies, regardless of
957 /// whether the property value has actually changed or not.
958 pub fn mark_dirty(&self) {
959 self.handle.mark_dirty(
960 #[cfg(slint_debug_property)]
961 self.debug_name.borrow().as_str(),
962 )
963 }
964
965 /// Mark that this property will never be modified again and that no tracking should be done
966 pub fn set_constant(&self) {
967 self.handle.set_constant();
968 }
969}
970
971#[test]
972fn properties_simple_test() {
973 use pin_weak::rc::PinWeak;
974 use std::rc::Rc;
975 fn g(prop: &Property<i32>) -> i32 {
976 unsafe { Pin::new_unchecked(prop).get() }
977 }
978
979 #[derive(Default)]
980 struct Component {
981 width: Property<i32>,
982 height: Property<i32>,
983 area: Property<i32>,
984 }
985
986 let compo = Rc::pin(Component::default());
987 let w = PinWeak::downgrade(compo.clone());
988 compo.area.set_binding(move || {
989 let compo = w.upgrade().unwrap();
990 g(&compo.width) * g(&compo.height)
991 });
992 compo.width.set(4);
993 compo.height.set(8);
994 assert_eq!(g(&compo.width), 4);
995 assert_eq!(g(&compo.height), 8);
996 assert_eq!(g(&compo.area), 4 * 8);
997
998 let w = PinWeak::downgrade(compo.clone());
999 compo.width.set_binding(move || {
1000 let compo = w.upgrade().unwrap();
1001 g(&compo.height) * 2
1002 });
1003 assert_eq!(g(&compo.width), 8 * 2);
1004 assert_eq!(g(&compo.height), 8);
1005 assert_eq!(g(&compo.area), 8 * 8 * 2);
1006}
1007
1008impl<T: PartialEq + Clone + 'static> Property<T> {
1009 /// Link two property such that any change to one property is affecting the other property as if they
1010 /// where, in fact, a single property.
1011 /// The value or binding of prop2 is kept.
1012 pub fn link_two_way(prop1: Pin<&Self>, prop2: Pin<&Self>) {
1013 struct TwoWayBinding<T> {
1014 common_property: Pin<Rc<Property<T>>>,
1015 }
1016 unsafe impl<T: PartialEq + Clone + 'static> BindingCallable for TwoWayBinding<T> {
1017 unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
1018 *(value as *mut T) = self.common_property.as_ref().get();
1019 BindingResult::KeepBinding
1020 }
1021
1022 unsafe fn intercept_set(self: Pin<&Self>, value: *const ()) -> bool {
1023 self.common_property.as_ref().set((*(value as *const T)).clone());
1024 true
1025 }
1026
1027 unsafe fn intercept_set_binding(
1028 self: Pin<&Self>,
1029 new_binding: *mut BindingHolder,
1030 ) -> bool {
1031 self.common_property.handle.set_binding_impl(new_binding);
1032 true
1033 }
1034
1035 const IS_TWO_WAY_BINDING: bool = true;
1036 }
1037
1038 #[cfg(slint_debug_property)]
1039 let debug_name = format!("<{}<=>{}>", prop1.debug_name.borrow(), prop2.debug_name.borrow());
1040
1041 let value = prop2.get_internal();
1042
1043 let prop1_handle_val = prop1.handle.handle.get();
1044 if prop1_handle_val & 0b10 == 0b10 {
1045 // Safety: the handle is a pointer to a binding
1046 let holder = unsafe { &*((prop1_handle_val & !0b11) as *const BindingHolder) };
1047 if holder.is_two_way_binding {
1048 unsafe {
1049 // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T>
1050 let holder =
1051 &*((prop1_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>);
1052 // Safety: TwoWayBinding's T is the same as the type for both properties
1053 prop2.handle.set_binding(
1054 TwoWayBinding { common_property: holder.binding.common_property.clone() },
1055 #[cfg(slint_debug_property)]
1056 debug_name.as_str(),
1057 );
1058 }
1059 prop2.set(value);
1060 return;
1061 }
1062 };
1063
1064 let prop2_handle_val = prop2.handle.handle.get();
1065 let handle = if prop2_handle_val & 0b10 == 0b10 {
1066 // Safety: the handle is a pointer to a binding
1067 let holder = unsafe { &*((prop2_handle_val & !0b11) as *const BindingHolder) };
1068 if holder.is_two_way_binding {
1069 unsafe {
1070 // Safety: the handle is a pointer to a binding whose B is a TwoWayBinding<T>
1071 let holder =
1072 &*((prop2_handle_val & !0b11) as *const BindingHolder<TwoWayBinding<T>>);
1073 // Safety: TwoWayBinding's T is the same as the type for both properties
1074 prop1.handle.set_binding(
1075 TwoWayBinding { common_property: holder.binding.common_property.clone() },
1076 #[cfg(slint_debug_property)]
1077 debug_name.as_str(),
1078 );
1079 }
1080 return;
1081 }
1082 // If prop2 is a binding, just "steal it"
1083 prop2.handle.handle.set(0);
1084 PropertyHandle { handle: Cell::new(prop2_handle_val) }
1085 } else {
1086 PropertyHandle::default()
1087 };
1088
1089 let common_property = Rc::pin(Property {
1090 handle,
1091 value: UnsafeCell::new(value),
1092 pinned: PhantomPinned,
1093 #[cfg(slint_debug_property)]
1094 debug_name: debug_name.clone().into(),
1095 });
1096 // Safety: TwoWayBinding's T is the same as the type for both properties
1097 unsafe {
1098 prop1.handle.set_binding(
1099 TwoWayBinding { common_property: common_property.clone() },
1100 #[cfg(slint_debug_property)]
1101 debug_name.as_str(),
1102 );
1103 prop2.handle.set_binding(
1104 TwoWayBinding { common_property },
1105 #[cfg(slint_debug_property)]
1106 debug_name.as_str(),
1107 );
1108 }
1109 }
1110}
1111
1112#[test]
1113fn property_two_ways_test() {
1114 let p1 = Rc::pin(Property::new(42));
1115 let p2 = Rc::pin(Property::new(88));
1116
1117 let depends = Box::pin(Property::new(0));
1118 depends.as_ref().set_binding({
1119 let p1 = p1.clone();
1120 move || p1.as_ref().get() + 8
1121 });
1122 assert_eq!(depends.as_ref().get(), 42 + 8);
1123 Property::link_two_way(p1.as_ref(), p2.as_ref());
1124 assert_eq!(p1.as_ref().get(), 88);
1125 assert_eq!(p2.as_ref().get(), 88);
1126 assert_eq!(depends.as_ref().get(), 88 + 8);
1127 p2.as_ref().set(5);
1128 assert_eq!(p1.as_ref().get(), 5);
1129 assert_eq!(p2.as_ref().get(), 5);
1130 assert_eq!(depends.as_ref().get(), 5 + 8);
1131 p1.as_ref().set(22);
1132 assert_eq!(p1.as_ref().get(), 22);
1133 assert_eq!(p2.as_ref().get(), 22);
1134 assert_eq!(depends.as_ref().get(), 22 + 8);
1135}
1136
1137#[test]
1138fn property_two_ways_test_binding() {
1139 let p1 = Rc::pin(Property::new(42));
1140 let p2 = Rc::pin(Property::new(88));
1141 let global = Rc::pin(Property::new(23));
1142 p2.as_ref().set_binding({
1143 let global = global.clone();
1144 move || global.as_ref().get() + 9
1145 });
1146
1147 let depends = Box::pin(Property::new(0));
1148 depends.as_ref().set_binding({
1149 let p1 = p1.clone();
1150 move || p1.as_ref().get() + 8
1151 });
1152
1153 Property::link_two_way(p1.as_ref(), p2.as_ref());
1154 assert_eq!(p1.as_ref().get(), 23 + 9);
1155 assert_eq!(p2.as_ref().get(), 23 + 9);
1156 assert_eq!(depends.as_ref().get(), 23 + 9 + 8);
1157 global.as_ref().set(55);
1158 assert_eq!(p1.as_ref().get(), 55 + 9);
1159 assert_eq!(p2.as_ref().get(), 55 + 9);
1160 assert_eq!(depends.as_ref().get(), 55 + 9 + 8);
1161}
1162
1163#[test]
1164fn property_two_ways_recurse_from_binding() {
1165 let xx = Rc::pin(Property::new(0));
1166
1167 let p1 = Rc::pin(Property::new(42));
1168 let p2 = Rc::pin(Property::new(88));
1169 let global = Rc::pin(Property::new(23));
1170
1171 let done = Rc::new(Cell::new(false));
1172 xx.set_binding({
1173 let p1 = p1.clone();
1174 let p2 = p2.clone();
1175 let global = global.clone();
1176 let xx_weak = pin_weak::rc::PinWeak::downgrade(xx.clone());
1177 move || {
1178 if !done.get() {
1179 done.set(true);
1180 Property::link_two_way(p1.as_ref(), p2.as_ref());
1181 let xx = xx_weak.upgrade().unwrap();
1182 p1.as_ref().set_binding(move || xx.as_ref().get() + 9);
1183 }
1184 global.as_ref().get() + 2
1185 }
1186 });
1187 assert_eq!(xx.as_ref().get(), 23 + 2);
1188 assert_eq!(p1.as_ref().get(), 23 + 2 + 9);
1189 assert_eq!(p2.as_ref().get(), 23 + 2 + 9);
1190
1191 global.as_ref().set(55);
1192 assert_eq!(p1.as_ref().get(), 55 + 2 + 9);
1193 assert_eq!(p2.as_ref().get(), 55 + 2 + 9);
1194 assert_eq!(xx.as_ref().get(), 55 + 2);
1195}
1196
1197#[test]
1198fn property_two_ways_binding_of_two_way_binding_first() {
1199 let p1_1 = Rc::pin(Property::new(2));
1200 let p1_2 = Rc::pin(Property::new(4));
1201 Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1202
1203 assert_eq!(p1_1.as_ref().get(), 4);
1204 assert_eq!(p1_2.as_ref().get(), 4);
1205
1206 let p2 = Rc::pin(Property::new(3));
1207 Property::link_two_way(p1_1.as_ref(), p2.as_ref());
1208
1209 assert_eq!(p1_1.as_ref().get(), 3);
1210 assert_eq!(p1_2.as_ref().get(), 3);
1211 assert_eq!(p2.as_ref().get(), 3);
1212
1213 p1_1.set(6);
1214
1215 assert_eq!(p1_1.as_ref().get(), 6);
1216 assert_eq!(p1_2.as_ref().get(), 6);
1217 assert_eq!(p2.as_ref().get(), 6);
1218
1219 p1_2.set(8);
1220
1221 assert_eq!(p1_1.as_ref().get(), 8);
1222 assert_eq!(p1_2.as_ref().get(), 8);
1223 assert_eq!(p2.as_ref().get(), 8);
1224
1225 p2.set(7);
1226
1227 assert_eq!(p1_1.as_ref().get(), 7);
1228 assert_eq!(p1_2.as_ref().get(), 7);
1229 assert_eq!(p2.as_ref().get(), 7);
1230}
1231
1232#[test]
1233fn property_two_ways_binding_of_two_way_binding_second() {
1234 let p1 = Rc::pin(Property::new(2));
1235 let p2_1 = Rc::pin(Property::new(3));
1236 let p2_2 = Rc::pin(Property::new(5));
1237 Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1238
1239 assert_eq!(p2_1.as_ref().get(), 5);
1240 assert_eq!(p2_2.as_ref().get(), 5);
1241
1242 Property::link_two_way(p1.as_ref(), p2_2.as_ref());
1243
1244 assert_eq!(p1.as_ref().get(), 5);
1245 assert_eq!(p2_1.as_ref().get(), 5);
1246 assert_eq!(p2_2.as_ref().get(), 5);
1247
1248 p1.set(6);
1249
1250 assert_eq!(p1.as_ref().get(), 6);
1251 assert_eq!(p2_1.as_ref().get(), 6);
1252 assert_eq!(p2_2.as_ref().get(), 6);
1253
1254 p2_1.set(7);
1255
1256 assert_eq!(p1.as_ref().get(), 7);
1257 assert_eq!(p2_1.as_ref().get(), 7);
1258 assert_eq!(p2_2.as_ref().get(), 7);
1259
1260 p2_2.set(9);
1261
1262 assert_eq!(p1.as_ref().get(), 9);
1263 assert_eq!(p2_1.as_ref().get(), 9);
1264 assert_eq!(p2_2.as_ref().get(), 9);
1265}
1266
1267#[test]
1268fn property_two_ways_binding_of_two_two_way_bindings() {
1269 let p1_1 = Rc::pin(Property::new(2));
1270 let p1_2 = Rc::pin(Property::new(4));
1271 Property::link_two_way(p1_1.as_ref(), p1_2.as_ref());
1272 assert_eq!(p1_1.as_ref().get(), 4);
1273 assert_eq!(p1_2.as_ref().get(), 4);
1274
1275 let p2_1 = Rc::pin(Property::new(3));
1276 let p2_2 = Rc::pin(Property::new(5));
1277 Property::link_two_way(p2_1.as_ref(), p2_2.as_ref());
1278
1279 assert_eq!(p2_1.as_ref().get(), 5);
1280 assert_eq!(p2_2.as_ref().get(), 5);
1281
1282 Property::link_two_way(p1_1.as_ref(), p2_2.as_ref());
1283
1284 assert_eq!(p1_1.as_ref().get(), 5);
1285 assert_eq!(p1_2.as_ref().get(), 5);
1286 assert_eq!(p2_1.as_ref().get(), 5);
1287 assert_eq!(p2_2.as_ref().get(), 5);
1288
1289 p1_1.set(6);
1290 assert_eq!(p1_1.as_ref().get(), 6);
1291 assert_eq!(p1_2.as_ref().get(), 6);
1292 assert_eq!(p2_1.as_ref().get(), 6);
1293 assert_eq!(p2_2.as_ref().get(), 6);
1294
1295 p1_2.set(8);
1296 assert_eq!(p1_1.as_ref().get(), 8);
1297 assert_eq!(p1_2.as_ref().get(), 8);
1298 assert_eq!(p2_1.as_ref().get(), 8);
1299 assert_eq!(p2_2.as_ref().get(), 8);
1300
1301 p2_1.set(7);
1302 assert_eq!(p1_1.as_ref().get(), 7);
1303 assert_eq!(p1_2.as_ref().get(), 7);
1304 assert_eq!(p2_1.as_ref().get(), 7);
1305 assert_eq!(p2_2.as_ref().get(), 7);
1306
1307 p2_2.set(9);
1308 assert_eq!(p1_1.as_ref().get(), 9);
1309 assert_eq!(p1_2.as_ref().get(), 9);
1310 assert_eq!(p2_1.as_ref().get(), 9);
1311 assert_eq!(p2_2.as_ref().get(), 9);
1312}
1313
1314mod change_tracker;
1315pub use change_tracker::*;
1316mod properties_animations;
1317pub use crate::items::StateInfo;
1318pub use properties_animations::*;
1319
1320struct StateInfoBinding<F> {
1321 dirty_time: Cell<Option<crate::animations::Instant>>,
1322 binding: F,
1323}
1324
1325unsafe impl<F: Fn() -> i32> crate::properties::BindingCallable for StateInfoBinding<F> {
1326 unsafe fn evaluate(self: Pin<&Self>, value: *mut ()) -> BindingResult {
1327 // Safety: We should only set this binding on a property of type StateInfo
1328 let value: &mut StateInfo = &mut *(value as *mut StateInfo);
1329 let new_state: i32 = (self.binding)();
1330 let timestamp: Option = self.dirty_time.take();
1331 if new_state != value.current_state {
1332 value.previous_state = value.current_state;
1333 value.change_time = timestamp.unwrap_or_else(crate::animations::current_tick);
1334 value.current_state = new_state;
1335 }
1336 BindingResult::KeepBinding
1337 }
1338
1339 fn mark_dirty(self: Pin<&Self>) {
1340 if self.dirty_time.get().is_none() {
1341 self.dirty_time.set(val:Some(crate::animations::current_tick()))
1342 }
1343 }
1344}
1345
1346/// Sets a binding that returns a state to a StateInfo property
1347pub fn set_state_binding(property: Pin<&Property<StateInfo>>, binding: impl Fn() -> i32 + 'static) {
1348 let bind_callable: StateInfoBinding … + 'static> = StateInfoBinding { dirty_time: Cell::new(None), binding };
1349 // Safety: The StateInfoBinding is a BindingCallable for type StateInfo
1350 unsafe {
1351 property.handle.set_binding(
1352 bind_callable,
1353 #[cfg(slint_debug_property)]
1354 property.debug_name.borrow().as_str(),
1355 )
1356 }
1357}
1358
1359#[doc(hidden)]
1360pub trait PropertyDirtyHandler {
1361 fn notify(self: Pin<&Self>);
1362}
1363
1364impl PropertyDirtyHandler for () {
1365 fn notify(self: Pin<&Self>) {}
1366}
1367
1368impl<F: Fn()> PropertyDirtyHandler for F {
1369 fn notify(self: Pin<&Self>) {
1370 (self.get_ref())()
1371 }
1372}
1373
1374/// This structure allow to run a closure that queries properties, and can report
1375/// if any property we accessed have become dirty
1376pub struct PropertyTracker<DirtyHandler = ()> {
1377 holder: BindingHolder<DirtyHandler>,
1378}
1379
1380impl Default for PropertyTracker<()> {
1381 fn default() -> Self {
1382 static VT: &BindingVTable = &BindingVTable {
1383 drop: |_| (),
1384 evaluate: |_, _| BindingResult::KeepBinding,
1385 mark_dirty: |_, _| (),
1386 intercept_set: |_, _| false,
1387 intercept_set_binding: |_, _| false,
1388 };
1389
1390 let holder: BindingHolder = BindingHolder {
1391 dependencies: Cell::new(0),
1392 dep_nodes: Default::default(),
1393 vtable: VT,
1394 dirty: Cell::new(true), // starts dirty so it evaluates the property when used
1395 is_two_way_binding: false,
1396 pinned: PhantomPinned,
1397 binding: (),
1398 #[cfg(slint_debug_property)]
1399 debug_name: "<PropertyTracker<()>>".into(),
1400 };
1401 Self { holder }
1402 }
1403}
1404
1405impl<DirtyHandler> Drop for PropertyTracker<DirtyHandler> {
1406 fn drop(&mut self) {
1407 unsafe {
1408 DependencyListHead::drop(self.holder.dependencies.as_ptr() as *mut DependencyListHead);
1409 }
1410 }
1411}
1412
1413impl<DirtyHandler: PropertyDirtyHandler> PropertyTracker<DirtyHandler> {
1414 #[cfg(slint_debug_property)]
1415 /// set the debug name when `cfg(slint_debug_property`
1416 pub fn set_debug_name(&mut self, debug_name: String) {
1417 self.holder.debug_name = debug_name;
1418 }
1419
1420 /// Register this property tracker as a dependency to the current binding/property tracker being evaluated
1421 pub fn register_as_dependency_to_current_binding(self: Pin<&Self>) {
1422 if CURRENT_BINDING.is_set() {
1423 CURRENT_BINDING.with(|cur_binding| {
1424 if let Some(cur_binding) = cur_binding {
1425 debug_assert!(!core::ptr::eq(
1426 self.holder.dependencies.get() as *const u32,
1427 (&CONSTANT_PROPERTY_SENTINEL) as *const u32,
1428 ));
1429 cur_binding.register_self_as_dependency(
1430 self.holder.dependencies.as_ptr() as *mut DependencyListHead,
1431 #[cfg(slint_debug_property)]
1432 &self.holder.debug_name,
1433 );
1434 }
1435 });
1436 }
1437 }
1438
1439 /// Any of the properties accessed during the last evaluation of the closure called
1440 /// from the last call to evaluate is potentially dirty.
1441 pub fn is_dirty(&self) -> bool {
1442 self.holder.dirty.get()
1443 }
1444
1445 /// Evaluate the function, and record dependencies of properties accessed within this function.
1446 /// If this is called during the evaluation of another property binding or property tracker, then
1447 /// any changes to accessed properties will also mark the other binding/tracker dirty.
1448 pub fn evaluate<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1449 self.register_as_dependency_to_current_binding();
1450 self.evaluate_as_dependency_root(f)
1451 }
1452
1453 /// Evaluate the function, and record dependencies of properties accessed within this function.
1454 /// If this is called during the evaluation of another property binding or property tracker, then
1455 /// any changes to accessed properties will not propagate to the other tracker.
1456 pub fn evaluate_as_dependency_root<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> R {
1457 // clear all the nodes so that we can start from scratch
1458 self.holder.dep_nodes.set(Default::default());
1459
1460 // Safety: it is safe to project the holder as we don't implement drop or unpin
1461 let pinned_holder = unsafe {
1462 self.map_unchecked(|s| {
1463 core::mem::transmute::<&BindingHolder<DirtyHandler>, &BindingHolder<()>>(&s.holder)
1464 })
1465 };
1466 let r = CURRENT_BINDING.set(Some(pinned_holder), f);
1467 self.holder.dirty.set(false);
1468 r
1469 }
1470
1471 /// Call [`Self::evaluate`] if and only if it is dirty.
1472 /// But register a dependency in any case.
1473 pub fn evaluate_if_dirty<R>(self: Pin<&Self>, f: impl FnOnce() -> R) -> Option<R> {
1474 self.register_as_dependency_to_current_binding();
1475 self.is_dirty().then(|| self.evaluate_as_dependency_root(f))
1476 }
1477
1478 /// Mark this PropertyTracker as dirty
1479 pub fn set_dirty(&self) {
1480 self.holder.dirty.set(true);
1481 unsafe { mark_dependencies_dirty(self.holder.dependencies.as_ptr() as *mut _) };
1482 }
1483
1484 /// Sets the specified callback handler function, which will be called if any
1485 /// properties that this tracker depends on becomes dirty.
1486 ///
1487 /// The `handler` `PropertyDirtyHandler` is a trait which is implemented for
1488 /// any `Fn()` closure
1489 ///
1490 /// Note that the handler will be invoked immediately when a property is modified or
1491 /// marked as dirty. In particular, the involved property are still in a locked
1492 /// state and should not be accessed while the handler is run. This function can be
1493 /// useful to mark some work to be done later.
1494 pub fn new_with_dirty_handler(handler: DirtyHandler) -> Self {
1495 /// Safety: _self must be a pointer to a `BindingHolder<DirtyHandler>`
1496 unsafe fn mark_dirty<B: PropertyDirtyHandler>(
1497 _self: *const BindingHolder,
1498 was_dirty: bool,
1499 ) {
1500 if !was_dirty {
1501 Pin::new_unchecked(&(*(_self as *const BindingHolder<B>)).binding).notify();
1502 }
1503 }
1504
1505 trait HasBindingVTable {
1506 const VT: &'static BindingVTable;
1507 }
1508 impl<B: PropertyDirtyHandler> HasBindingVTable for B {
1509 const VT: &'static BindingVTable = &BindingVTable {
1510 drop: |_| (),
1511 evaluate: |_, _| BindingResult::KeepBinding,
1512 mark_dirty: mark_dirty::<B>,
1513 intercept_set: |_, _| false,
1514 intercept_set_binding: |_, _| false,
1515 };
1516 }
1517
1518 let holder = BindingHolder {
1519 dependencies: Cell::new(0),
1520 dep_nodes: Default::default(),
1521 vtable: <DirtyHandler as HasBindingVTable>::VT,
1522 dirty: Cell::new(true), // starts dirty so it evaluates the property when used
1523 is_two_way_binding: false,
1524 pinned: PhantomPinned,
1525 binding: handler,
1526 #[cfg(slint_debug_property)]
1527 debug_name: "<PropertyTracker>".into(),
1528 };
1529 Self { holder }
1530 }
1531}
1532
1533#[test]
1534fn test_property_listener_scope() {
1535 let scope = Box::pin(PropertyTracker::default());
1536 let prop1 = Box::pin(Property::new(42));
1537 assert!(scope.is_dirty()); // It is dirty at the beginning
1538
1539 let r = scope.as_ref().evaluate(|| prop1.as_ref().get());
1540 assert_eq!(r, 42);
1541 assert!(!scope.is_dirty()); // It is no longer dirty
1542 prop1.as_ref().set(88);
1543 assert!(scope.is_dirty()); // now dirty for prop1 changed.
1544 let r = scope.as_ref().evaluate(|| prop1.as_ref().get() + 1);
1545 assert_eq!(r, 89);
1546 assert!(!scope.is_dirty());
1547 let r = scope.as_ref().evaluate(|| 12);
1548 assert_eq!(r, 12);
1549 assert!(!scope.is_dirty());
1550 prop1.as_ref().set(1);
1551 assert!(!scope.is_dirty());
1552 scope.as_ref().evaluate_if_dirty(|| panic!("should not be dirty"));
1553 scope.set_dirty();
1554 let mut ok = false;
1555 scope.as_ref().evaluate_if_dirty(|| ok = true);
1556 assert!(ok);
1557}
1558
1559#[test]
1560fn test_nested_property_trackers() {
1561 let tracker1 = Box::pin(PropertyTracker::default());
1562 let tracker2 = Box::pin(PropertyTracker::default());
1563 let prop = Box::pin(Property::new(42));
1564
1565 let r = tracker1.as_ref().evaluate(|| tracker2.as_ref().evaluate(|| prop.as_ref().get()));
1566 assert_eq!(r, 42);
1567
1568 prop.as_ref().set(1);
1569 assert!(tracker2.as_ref().is_dirty());
1570 assert!(tracker1.as_ref().is_dirty());
1571
1572 let r = tracker1
1573 .as_ref()
1574 .evaluate(|| tracker2.as_ref().evaluate_as_dependency_root(|| prop.as_ref().get()));
1575 assert_eq!(r, 1);
1576 prop.as_ref().set(100);
1577 assert!(tracker2.as_ref().is_dirty());
1578 assert!(!tracker1.as_ref().is_dirty());
1579}
1580
1581#[test]
1582fn test_property_dirty_handler() {
1583 let call_flag = Rc::new(Cell::new(false));
1584 let tracker = Box::pin(PropertyTracker::new_with_dirty_handler({
1585 let call_flag = call_flag.clone();
1586 move || {
1587 (*call_flag).set(true);
1588 }
1589 }));
1590 let prop = Box::pin(Property::new(42));
1591
1592 let r = tracker.as_ref().evaluate(|| prop.as_ref().get());
1593
1594 assert_eq!(r, 42);
1595 assert!(!tracker.as_ref().is_dirty());
1596 assert!(!call_flag.get());
1597
1598 prop.as_ref().set(100);
1599 assert!(tracker.as_ref().is_dirty());
1600 assert!(call_flag.get());
1601
1602 // Repeated changes before evaluation should not trigger further
1603 // change handler calls, otherwise it would be a notification storm.
1604 call_flag.set(false);
1605 prop.as_ref().set(101);
1606 assert!(tracker.as_ref().is_dirty());
1607 assert!(!call_flag.get());
1608}
1609
1610#[test]
1611fn test_property_tracker_drop() {
1612 let outer_tracker = Box::pin(PropertyTracker::default());
1613 let inner_tracker = Box::pin(PropertyTracker::default());
1614 let prop = Box::pin(Property::new(42));
1615
1616 let r =
1617 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1618 assert_eq!(r, 42);
1619
1620 drop(inner_tracker);
1621 prop.as_ref().set(200); // don't crash
1622}
1623
1624#[test]
1625fn test_nested_property_tracker_dirty() {
1626 let outer_tracker = Box::pin(PropertyTracker::default());
1627 let inner_tracker = Box::pin(PropertyTracker::default());
1628 let prop = Box::pin(Property::new(42));
1629
1630 let r =
1631 outer_tracker.as_ref().evaluate(|| inner_tracker.as_ref().evaluate(|| prop.as_ref().get()));
1632 assert_eq!(r, 42);
1633
1634 assert!(!outer_tracker.is_dirty());
1635 assert!(!inner_tracker.is_dirty());
1636
1637 // Let's pretend that there was another dependency unaccounted first, mark the inner tracker as dirty
1638 // by hand.
1639 inner_tracker.as_ref().set_dirty();
1640 assert!(outer_tracker.is_dirty());
1641}
1642
1643#[test]
1644#[allow(clippy::redundant_closure)]
1645fn test_nested_property_tracker_evaluate_if_dirty() {
1646 let outer_tracker = Box::pin(PropertyTracker::default());
1647 let inner_tracker = Box::pin(PropertyTracker::default());
1648 let prop = Box::pin(Property::new(42));
1649
1650 let mut cache = 0;
1651 let mut cache_or_evaluate = || {
1652 if let Some(x) = inner_tracker.as_ref().evaluate_if_dirty(|| prop.as_ref().get() + 1) {
1653 cache = x;
1654 }
1655 cache
1656 };
1657 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1658 assert_eq!(r, 43);
1659 assert!(!outer_tracker.is_dirty());
1660 assert!(!inner_tracker.is_dirty());
1661 prop.as_ref().set(11);
1662 assert!(outer_tracker.is_dirty());
1663 assert!(inner_tracker.is_dirty());
1664 let r = outer_tracker.as_ref().evaluate(|| cache_or_evaluate());
1665 assert_eq!(r, 12);
1666}
1667
1668#[cfg(feature = "ffi")]
1669pub(crate) mod ffi;
1670