Skip to main content

pax_runtime_api/
variables.rs

1//! LocalProperty adapters that expose runtime values to expression scopes.
2
3use super::*;
4
5#[cfg(test)]
6mod tests;
7
8/// Runtime property adapter used by expression scopes.
9#[derive(Clone)]
10pub struct Variable {
11    untyped_property: UntypedProperty,
12    converted_to_pax_value: LocalProperty<PaxValue>,
13    rust_type: std::any::TypeId,
14}
15
16impl Variable {
17    /// Wraps an untyped property and exposes it as a `PaxValue`.
18    pub fn new<T: PropertyValue + ToPaxValue>(untyped_property: UntypedProperty) -> Self {
19        Self::new_from_typed_property(LocalProperty::<T>::new_from_untyped(
20            untyped_property.clone(),
21        ))
22    }
23
24    /// Wraps a typed property and exposes it as a `PaxValue`.
25    pub fn new_from_typed_property<T: PropertyValue + ToPaxValue>(
26        property: impl PropertyBinding<T>,
27    ) -> Self {
28        let property = property.local();
29        let untyped_property = property.untyped();
30        let deps = [untyped_property.clone()];
31        let nested_watch = LocalProperty::<()>::default();
32        let pax_value_prop = LocalProperty::computed(
33            move || {
34                let value = property.get();
35                let dependencies = value.nested_properties();
36                if dependencies.is_empty() {
37                    nested_watch.replace_with(LocalProperty::default());
38                } else {
39                    let source = property.clone();
40                    let initial = std::cell::Cell::new(true);
41                    nested_watch.replace_with(LocalProperty::computed(
42                        move || {
43                            if !initial.replace(false) {
44                                source.invalidate();
45                            }
46                        },
47                        &dependencies,
48                    ));
49                    crate::properties::register_effect_property(&nested_watch);
50                    nested_watch.get();
51                }
52                value.to_pax_value_in_graph()
53            },
54            &deps,
55        );
56        Variable {
57            untyped_property,
58            converted_to_pax_value: pax_value_prop,
59            rust_type: std::any::TypeId::of::<T>(),
60        }
61    }
62
63    /// Creates an independent one-way binding when exact typed forwarding is
64    /// safe. Unlike a double binding, writes/easing on the result never mutate
65    /// the source. Mismatched types and custom conversions return `None`.
66    pub fn try_typed_binding<T: PropertyValue + CoercionRules>(
67        &self,
68        name: &str,
69    ) -> Option<LocalProperty<T>> {
70        if self.rust_type != std::any::TypeId::of::<T>()
71            || !self.untyped_property.has_value_type::<T>()
72            || !pax_value::is_typed_binding_safe::<T>()
73        {
74            return None;
75        }
76        // Both adapter and storage types were checked. Replacement preserves T
77        // and the generational property handle; no borrowed value escapes.
78        let source = LocalProperty::<T>::new_from_untyped(self.untyped_property.clone());
79        Some(LocalProperty::computed_with_name(
80            move || source.get(),
81            &[self.untyped_property.clone()],
82            name,
83        ))
84    }
85
86    // Returns the underlying untyped property for dependency graph wiring.
87    pub fn get_untyped_property(&self) -> &UntypedProperty {
88        &self.untyped_property
89    }
90
91    /// Reads the current value as a `PaxValue`.
92    pub fn get_as_pax_value(&self) -> PaxValue {
93        self.converted_to_pax_value.get()
94    }
95
96    /// Reads the current `PaxValue` by reference.
97    pub fn read_pax_value_ref<V>(&self, f: impl FnOnce(&PaxValue) -> V) -> V {
98        self.converted_to_pax_value.read(f)
99    }
100}