use crate::controls::INameScope;
use crate::data::converters::IValueConverter;
use crate::data::core::expression_nodes::ExpressionNode;
use crate::data::core::parsers::ExpressionNodeFactory;
use crate::data::compiled_binding_path::TypedPropertyElement;
use crate::data::core::plugins::property_value_type;
use crate::data::core::{BindingExpression, BindingExpressionOptions, TargetTypeConverter};
use crate::data::{
BindingBase, BindingExpressionBase, BindingMode, BindingPriority, CompiledBindingPath, UpdateSourceTrigger,
};
use crate::utilities::CultureInfo;
use crate::{BoxedValue, FerroObject, FerroProperty, Ref, StyledElement, WeakRef};
use std::rc::{Rc, Weak};
pub(crate) fn resolve_defaults_from_metadata(
mode: BindingMode,
update_source_trigger: UpdateSourceTrigger,
target: &FerroObject,
target_property: Option<&'static FerroProperty>,
) -> (BindingMode, UpdateSourceTrigger) {
let trigger = if update_source_trigger == UpdateSourceTrigger::Default {
UpdateSourceTrigger::PropertyChanged
} else {
update_source_trigger
};
let mode = if mode == BindingMode::Default {
match target_property {
Some(p) => p.get_metadata_for(target).default_binding_mode(),
None => BindingMode::OneWay,
}
} else {
mode
};
(mode, trigger)
}
pub(crate) fn create_data_context_node(target_property: Option<&'static FerroProperty>) -> Rc<dyn ExpressionNode> {
ExpressionNodeFactory::create_data_context(target_property)
}
macro_rules! binding_properties {
(
$(#[$struct_meta:meta])*
pub struct $name:ident {
$(
$(#[$meta:meta])*
$field:ident / $set:ident / $with:ident : $ty:ty = $default:expr,
)*
}
) => {
$(#[$struct_meta])*
pub struct $name {
$($field: ::std::cell::RefCell<$ty>,)*
}
impl Default for $name {
fn default() -> Self {
Self { $($field: ::std::cell::RefCell::new($default),)* }
}
}
impl PartialEq for $name {
#[inline]
fn eq(&self, other: &Self) -> bool {
::std::ptr::eq(self, other)
}
}
impl $name {
$(
$(#[$meta])*
pub fn $field(&self) -> $ty {
self.$field.borrow().clone()
}
pub fn $set(&self, value: $ty) {
*self.$field.borrow_mut() = value;
}
pub fn $with(self: ::std::rc::Rc<Self>, value: $ty) -> ::std::rc::Rc<Self> {
self.$set(value);
self
}
)*
}
};
}
pub(crate) use binding_properties;
pub(crate) fn is_unset(value: &Option<BoxedValue>) -> bool {
value.as_ref().is_some_and(|v| v.is::<crate::UnsetValueType>())
}
pub(crate) fn explicit_source(source: Option<BoxedValue>) -> Option<Option<BoxedValue>> {
if is_unset(&source) {
None
} else {
Some(source)
}
}
binding_properties! {
pub struct CompiledBinding {
converter / set_converter / with_converter_value: Option<Rc<dyn IValueConverter>> = None,
converter_culture / set_converter_culture / with_converter_culture: Option<CultureInfo> = None,
converter_parameter / set_converter_parameter / with_converter_parameter: Option<BoxedValue> = None,
delay / set_delay / with_delay: i32 = 0,
fallback_value / set_fallback_value / with_fallback_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
mode / set_mode / with_mode: BindingMode = BindingMode::Default,
path / set_path / with_path: Option<CompiledBindingPath> = None,
priority / set_priority / with_priority: BindingPriority = BindingPriority::LocalValue,
source / set_source / with_source: Option<BoxedValue> = Some(FerroProperty::unset_value()),
string_format / set_string_format / with_string_format: Option<String> = None,
target_null_value / set_target_null_value / with_target_null_value: Option<BoxedValue> = Some(FerroProperty::unset_value()),
update_source_trigger / set_update_source_trigger / with_update_source_trigger: UpdateSourceTrigger = UpdateSourceTrigger::Default,
default_anchor / set_default_anchor / with_default_anchor: Option<WeakRef<FerroObject>> = None,
name_scope / set_name_scope / with_name_scope: Option<Weak<dyn INameScope>> = None,
}
}
impl CompiledBinding {
pub fn empty() -> Rc<Self> {
Rc::new(Self::default())
}
pub fn new(path: CompiledBindingPath) -> Rc<Self> {
Self::empty().with_path(Some(path))
}
pub fn with_converter(self: Rc<Self>, converter: Rc<dyn IValueConverter>) -> Rc<Self> {
self.with_converter_value(Some(converter))
}
}
impl CompiledBinding {
fn typed_element_for(
&self,
target: &FerroObject,
target_property: Option<&'static FerroProperty>,
) -> Option<Rc<dyn TypedPropertyElement>> {
let typed = self.path.borrow().as_ref()?.single_typed_element()?.clone();
if !is_unset(&self.source.borrow()) {
return None;
}
if self.converter.borrow().is_some()
|| !is_unset(&self.fallback_value.borrow())
|| self.string_format.borrow().is_some()
|| !is_unset(&self.target_null_value.borrow())
|| !matches!(
self.update_source_trigger(),
UpdateSourceTrigger::Default | UpdateSourceTrigger::PropertyChanged
)
|| self.delay() != 0
{
return None;
}
let target_property = target_property?;
let target_type = property_value_type(target_property);
if !crate::data::core::ValueTypes::is_assignable(typed.value_type(), target_type) {
return None;
}
if !target.is::<StyledElement>() {
return None;
}
if target_property.id() == StyledElement::data_context_property().id() {
return None;
}
if target_property.get_metadata_for(target).enable_data_validation() == Some(true) {
return None;
}
let (mode, _) =
resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, Some(target_property));
if matches!(mode, BindingMode::TwoWay | BindingMode::OneWayToSource)
&& (!typed.can_set() || !crate::data::core::ValueTypes::is_assignable(target_type, typed.value_type()))
{
return None;
}
Some(typed)
}
}
impl BindingBase for CompiledBinding {
fn as_any(&self) -> Option<&dyn std::any::Any> {
Some(self)
}
fn create_instance(
&self,
target: &FerroObject,
target_property: Option<&'static FerroProperty>,
anchor: Option<&Ref<FerroObject>>,
) -> Rc<dyn BindingExpressionBase> {
if let Some(typed) = self.typed_element_for(target, target_property) {
let (mode, _) =
resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, target_property);
return typed.create_expression(mode, self.priority());
}
let enable_data_validation =
target_property.is_some_and(|p| p.get_metadata_for(target).enable_data_validation() == Some(true));
let mut nodes: Vec<Rc<dyn ExpressionNode>> = Vec::new();
let path = self.path();
let is_rooted = path.as_ref().is_some_and(|p| p.build_expression(&mut nodes));
let explicit_source = explicit_source(self.source());
if explicit_source.is_none() && !is_rooted {
nodes.insert(0, create_data_context_node(target_property));
}
let target_ref = target.to_ref();
let default_anchor = self.default_anchor().as_ref().and_then(WeakRef::upgrade);
let anchor = anchor.or(default_anchor.as_ref());
let source = match nodes.first().and_then(|n| n.as_source_node()) {
Some(source_node) => source_node.select_source(explicit_source.as_ref(), &target_ref, anchor),
None => match &explicit_source {
Some(source) => source.clone(),
None => Some(Rc::new(target_ref.clone()) as BoxedValue),
},
};
let (mode, trigger) =
resolve_defaults_from_metadata(self.mode(), self.update_source_trigger(), target, target_property);
BindingExpression::new(
source,
nodes,
BindingExpressionOptions {
delay: self.delay(),
fallback_value: self.fallback_value(),
converter: self.converter(),
converter_culture: self.converter_culture(),
converter_parameter: self.converter_parameter(),
enable_data_validation,
mode,
priority: self.priority(),
string_format: self.string_format(),
target_null_value: self.target_null_value(),
target_property,
target_type_converter: Some(TargetTypeConverter::get_default_converter()),
update_source_trigger: trigger,
},
)
}
}