Skip to main content

ferroui_base/data/core/
indexer_binding_expression.rs

1use super::{
2    impl_untyped_binding_expression, Publish, UntypedBindingExpression, UntypedBindingExpressionBase, ValueType, ValueTypes,
3};
4use crate::data::{BindingMode, BindingPriority};
5use crate::reactive::IDisposable;
6use crate::{BoxedValue, FerroObject, FerroProperty, WeakRef};
7use std::cell::RefCell;
8use std::rc::{Rc, Weak};
9
10/// The binding expression of an
11/// [`IndexerBinding`](crate::data::IndexerBinding): binds a property of the
12/// target to a registered property of another object.
13///
14/// The source and the target are held weakly: unlike the managed
15/// implementation there is no collector for the reference cycle that a
16/// binding between two objects (or of an object to itself) would otherwise
17/// create.
18pub struct IndexerBindingExpression {
19    this: Weak<IndexerBindingExpression>,
20    base: UntypedBindingExpressionBase,
21    source: WeakRef<FerroObject>,
22    source_property: &'static FerroProperty,
23    target: WeakRef<FerroObject>,
24    target_property: Option<&'static FerroProperty>,
25    mode: BindingMode,
26    source_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
27    target_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
28}
29
30impl IndexerBindingExpression {
31    pub fn new(
32        source: &FerroObject,
33        source_property: &'static FerroProperty,
34        target: &FerroObject,
35        target_property: Option<&'static FerroProperty>,
36        mode: BindingMode,
37    ) -> Rc<Self> {
38        Rc::new_cyclic(|this: &Weak<IndexerBindingExpression>| Self {
39            this: this.clone(),
40            base: UntypedBindingExpressionBase::new(this.clone(), BindingPriority::LocalValue, None, false),
41            source: source.to_weak(),
42            source_property,
43            target: target.to_weak(),
44            target_property,
45            mode,
46            source_subscription: RefCell::new(None),
47            target_subscription: RefCell::new(None),
48        })
49    }
50
51    fn publish_source_value(&self) {
52        if let Some(source) = self.source.upgrade() {
53            let value = source.get_value_untyped(self.source_property);
54            let value = match self.target_property {
55                Some(target_property) => cast_to_property(value, target_property),
56                None => value,
57            };
58            self.base.publish_value(Publish::Value(Some(value)), None, false);
59        }
60    }
61}
62
63/// The value of a property as the value of another property receives it.
64///
65/// Upstream publishes `_source.GetValue(_sourceProperty)`, an `object`: what
66/// reaches the other property is the value itself, whatever the declared
67/// type of the property it was read from, and the property takes it if the
68/// value is of its type (the cast `value is T` of the untyped route). The
69/// untyped value of a property of the port has the declared type of the
70/// property (`Option<BoxedValue>` for a property of type `object`,
71/// `Option<T>` for a nullable one), so the value is taken out of that type
72/// and cast to the type of the receiving property: the text a content
73/// property holds is the text of a text property bound to it. A value that
74/// is not of the receiving type is passed on as it is, and is the invalid
75/// value it is upstream.
76fn cast_to_property(value: BoxedValue, property: &'static FerroProperty) -> BoxedValue {
77    let target = ValueType::new(property.property_type(), property.property_type_name());
78    if ValueType::of_value(&*value).id() == target.id() {
79        return value;
80    }
81
82    let converted = match ValueTypes::normalize(value.clone()) {
83        Some(inner) => ValueTypes::try_cast(&inner, target),
84        None => ValueTypes::null_value(target),
85    };
86    converted.unwrap_or(value)
87}
88
89impl UntypedBindingExpression for IndexerBindingExpression {
90    fn base(&self) -> &UntypedBindingExpressionBase {
91        &self.base
92    }
93
94    fn description(&self) -> String {
95        format!("IndexerBinding {})", self.source_property.name())
96    }
97
98    fn write_value_to_source(&self, value: Option<BoxedValue>) -> bool {
99        if let (Some(source), Some(value)) = (self.source.upgrade(), value) {
100            source.set_value_untyped(self.source_property, (*value).as_any(), BindingPriority::LocalValue);
101        }
102        true
103    }
104
105    fn start_core(&self) {
106        if matches!(self.mode, BindingMode::TwoWay | BindingMode::OneWayToSource) {
107            if let (Some(target), Some(target_property)) = (self.target.upgrade(), self.target_property) {
108                let weak = self.this.clone();
109                let id = target_property.id();
110                let subscription = target.property_changed(move |e| {
111                    if e.property().id() != id {
112                        return;
113                    }
114                    let Some(this) = weak.upgrade() else { return };
115                    if let Some(source) = this.source.upgrade() {
116                        source.set_value_untyped(this.source_property, e.new_value(), BindingPriority::LocalValue);
117                    }
118                });
119                self.target_subscription.replace(Some(subscription));
120            }
121        }
122
123        if self.mode != BindingMode::OneWayToSource {
124            if let Some(source) = self.source.upgrade() {
125                let weak = self.this.clone();
126                let id = self.source_property.id();
127                let subscription = source.property_changed(move |e| {
128                    if e.property().id() == id {
129                        if let Some(this) = weak.upgrade() {
130                            this.publish_source_value();
131                        }
132                    }
133                });
134                self.source_subscription.replace(Some(subscription));
135            }
136            self.publish_source_value();
137        }
138
139        if self.mode == BindingMode::OneTime {
140            self.stop();
141        }
142    }
143
144    fn stop_core(&self) {
145        let subscription = self.source_subscription.borrow_mut().take();
146        if let Some(s) = subscription {
147            s.dispose();
148        }
149        let subscription = self.target_subscription.borrow_mut().take();
150        if let Some(s) = subscription {
151            s.dispose();
152        }
153    }
154}
155
156impl_untyped_binding_expression!(IndexerBindingExpression);
157
158impl Drop for IndexerBindingExpression {
159    fn drop(&mut self) {
160        if let Some(s) = self.source_subscription.get_mut().take() {
161            s.dispose();
162        }
163        if let Some(s) = self.target_subscription.get_mut().take() {
164            s.dispose();
165        }
166    }
167}