use ferroui_base::data::core::IPropertyInfo;
use ferroui_base::data::{BindingBase, BindingError};
use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
use ferroui_base::reactive::IDisposable;
use ferroui_base::{BoxedValue, FerroObject, FerroProperty, Ref, StyledElement, WeakRef};
use std::any::Any;
use std::cell::RefCell;
use std::rc::Rc;
pub struct DelayedBinding;
enum Entry {
Binding { property: &'static FerroProperty, binding: Rc<dyn BindingBase> },
ClrPropertyValue { property: Rc<dyn IPropertyInfo>, value: Rc<dyn Fn(&StyledElement) -> Option<BoxedValue>> },
}
impl Entry {
fn apply(&self, control: &StyledElement) {
match self {
Entry::Binding { property, binding } => {
control.bind_binding(property, &**binding);
}
Entry::ClrPropertyValue { property, value } => {
let target: Ref<FerroObject> = control.to_ref().upcast();
if let Err(e) = property.set(&target, value(control).as_ref()) {
if let Some(log) = Logger::try_get(LogEventLevel::Error, LogArea::PROPERTY) {
let target_type = control.get_type().name();
log.log_with_values(
Some(control as &dyn Any),
"Error setting {Property} on {Target}: {Exception}",
&[&property.name(), &target_type, &e],
);
}
}
}
}
}
}
struct Pending {
target: WeakRef<StyledElement>,
entries: Vec<Entry>,
subscription: Option<Rc<dyn IDisposable>>,
}
ferroui_base::ferro_thread_local! {
static ENTRIES: RefCell<Vec<Pending>> = const { RefCell::new(Vec::new()) };
}
impl DelayedBinding {
pub fn add(target: &StyledElement, property: &'static FerroProperty, binding: Rc<dyn BindingBase>) {
if target.is_initialized() {
target.bind_binding(property, &*binding);
} else {
Self::add_entry(target, Entry::Binding { property, binding });
}
}
pub fn add_value(
target: &StyledElement,
property: Rc<dyn IPropertyInfo>,
value: impl Fn(&StyledElement) -> Option<BoxedValue> + 'static,
) -> Result<(), BindingError> {
if target.is_initialized() {
let object: Ref<FerroObject> = target.to_ref().upcast();
property.set(&object, value(target).as_ref())
} else {
Self::add_entry(target, Entry::ClrPropertyValue { property, value: Rc::new(value) });
Ok(())
}
}
fn add_entry(target: &StyledElement, entry: Entry) {
let target_ref = target.to_ref();
let exists = ENTRIES.with(|entries| {
let mut entries = entries.borrow_mut();
entries.retain(|p| p.target.upgrade().is_some());
match entries.iter_mut().find(|p| p.target.points_to(&target_ref)) {
Some(pending) => {
pending.entries.push(entry);
None
}
None => Some(entry),
}
});
if let Some(entry) = exists {
ENTRIES.with(|entries| {
entries.borrow_mut().push(Pending {
target: target_ref.downgrade(),
entries: vec![entry],
subscription: None,
})
});
let weak = target_ref.downgrade();
let subscription = target.initialized(move || {
if let Some(target) = weak.upgrade() {
Self::apply_bindings(&target);
}
});
ENTRIES.with(|entries| {
if let Some(pending) = entries.borrow_mut().iter_mut().find(|p| p.target.points_to(&target_ref)) {
pending.subscription = Some(subscription);
}
});
}
}
pub fn apply_bindings(control: &StyledElement) {
let control_ref = control.to_ref();
let pending = ENTRIES.with(|entries| {
let mut entries = entries.borrow_mut();
let index = entries.iter().position(|p| p.target.points_to(&control_ref))?;
Some(entries.remove(index))
});
if let Some(pending) = pending {
for entry in &pending.entries {
entry.apply(control);
}
if let Some(subscription) = pending.subscription {
subscription.dispose();
}
}
}
}