ferroui-base 0.3.0

The base library of FerroUI: the property system, layout, input, styling, media, text, animation, data binding and the compositor
Documentation
use super::{
    impl_untyped_binding_expression, Publish, UntypedBindingExpression, UntypedBindingExpressionBase, ValueType, ValueTypes,
};
use crate::data::{BindingMode, BindingPriority};
use crate::reactive::IDisposable;
use crate::{BoxedValue, FerroObject, FerroProperty, WeakRef};
use std::cell::RefCell;
use std::rc::{Rc, Weak};

/// The binding expression of an
/// [`IndexerBinding`](crate::data::IndexerBinding): binds a property of the
/// target to a registered property of another object.
///
/// The source and the target are held weakly: unlike the managed
/// implementation there is no collector for the reference cycle that a
/// binding between two objects (or of an object to itself) would otherwise
/// create.
pub struct IndexerBindingExpression {
    this: Weak<IndexerBindingExpression>,
    base: UntypedBindingExpressionBase,
    source: WeakRef<FerroObject>,
    source_property: &'static FerroProperty,
    target: WeakRef<FerroObject>,
    target_property: Option<&'static FerroProperty>,
    mode: BindingMode,
    source_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
    target_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
}

impl IndexerBindingExpression {
    pub fn new(
        source: &FerroObject,
        source_property: &'static FerroProperty,
        target: &FerroObject,
        target_property: Option<&'static FerroProperty>,
        mode: BindingMode,
    ) -> Rc<Self> {
        Rc::new_cyclic(|this: &Weak<IndexerBindingExpression>| Self {
            this: this.clone(),
            base: UntypedBindingExpressionBase::new(this.clone(), BindingPriority::LocalValue, None, false),
            source: source.to_weak(),
            source_property,
            target: target.to_weak(),
            target_property,
            mode,
            source_subscription: RefCell::new(None),
            target_subscription: RefCell::new(None),
        })
    }

    fn publish_source_value(&self) {
        if let Some(source) = self.source.upgrade() {
            let value = source.get_value_untyped(self.source_property);
            let value = match self.target_property {
                Some(target_property) => cast_to_property(value, target_property),
                None => value,
            };
            self.base.publish_value(Publish::Value(Some(value)), None, false);
        }
    }
}

/// The value of a property as the value of another property receives it.
///
/// Upstream publishes `_source.GetValue(_sourceProperty)`, an `object`: what
/// reaches the other property is the value itself, whatever the declared
/// type of the property it was read from, and the property takes it if the
/// value is of its type (the cast `value is T` of the untyped route). The
/// untyped value of a property of the port has the declared type of the
/// property (`Option<BoxedValue>` for a property of type `object`,
/// `Option<T>` for a nullable one), so the value is taken out of that type
/// and cast to the type of the receiving property: the text a content
/// property holds is the text of a text property bound to it. A value that
/// is not of the receiving type is passed on as it is, and is the invalid
/// value it is upstream.
fn cast_to_property(value: BoxedValue, property: &'static FerroProperty) -> BoxedValue {
    let target = ValueType::new(property.property_type(), property.property_type_name());
    if ValueType::of_value(&*value).id() == target.id() {
        return value;
    }

    let converted = match ValueTypes::normalize(value.clone()) {
        Some(inner) => ValueTypes::try_cast(&inner, target),
        None => ValueTypes::null_value(target),
    };
    converted.unwrap_or(value)
}

impl UntypedBindingExpression for IndexerBindingExpression {
    fn base(&self) -> &UntypedBindingExpressionBase {
        &self.base
    }

    fn description(&self) -> String {
        format!("IndexerBinding {})", self.source_property.name())
    }

    fn write_value_to_source(&self, value: Option<BoxedValue>) -> bool {
        if let (Some(source), Some(value)) = (self.source.upgrade(), value) {
            source.set_value_untyped(self.source_property, (*value).as_any(), BindingPriority::LocalValue);
        }
        true
    }

    fn start_core(&self) {
        if matches!(self.mode, BindingMode::TwoWay | BindingMode::OneWayToSource) {
            if let (Some(target), Some(target_property)) = (self.target.upgrade(), self.target_property) {
                let weak = self.this.clone();
                let id = target_property.id();
                let subscription = target.property_changed(move |e| {
                    if e.property().id() != id {
                        return;
                    }
                    let Some(this) = weak.upgrade() else { return };
                    if let Some(source) = this.source.upgrade() {
                        source.set_value_untyped(this.source_property, e.new_value(), BindingPriority::LocalValue);
                    }
                });
                self.target_subscription.replace(Some(subscription));
            }
        }

        if self.mode != BindingMode::OneWayToSource {
            if let Some(source) = self.source.upgrade() {
                let weak = self.this.clone();
                let id = self.source_property.id();
                let subscription = source.property_changed(move |e| {
                    if e.property().id() == id {
                        if let Some(this) = weak.upgrade() {
                            this.publish_source_value();
                        }
                    }
                });
                self.source_subscription.replace(Some(subscription));
            }
            self.publish_source_value();
        }

        if self.mode == BindingMode::OneTime {
            self.stop();
        }
    }

    fn stop_core(&self) {
        let subscription = self.source_subscription.borrow_mut().take();
        if let Some(s) = subscription {
            s.dispose();
        }
        let subscription = self.target_subscription.borrow_mut().take();
        if let Some(s) = subscription {
            s.dispose();
        }
    }
}

impl_untyped_binding_expression!(IndexerBindingExpression);

impl Drop for IndexerBindingExpression {
    fn drop(&mut self) {
        if let Some(s) = self.source_subscription.get_mut().take() {
            s.dispose();
        }
        if let Some(s) = self.target_subscription.get_mut().take() {
            s.dispose();
        }
    }
}