ferroui_markup/markup/data/
delayed_binding.rs1use ferroui_base::data::core::IPropertyInfo;
2use ferroui_base::data::{BindingBase, BindingError};
3use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
4use ferroui_base::reactive::IDisposable;
5use ferroui_base::{BoxedValue, FerroObject, FerroProperty, Ref, StyledElement, WeakRef};
6use std::any::Any;
7use std::cell::RefCell;
8use std::rc::Rc;
9
10pub struct DelayedBinding;
19
20enum Entry {
21 Binding { property: &'static FerroProperty, binding: Rc<dyn BindingBase> },
22 ClrPropertyValue { property: Rc<dyn IPropertyInfo>, value: Rc<dyn Fn(&StyledElement) -> Option<BoxedValue>> },
23}
24
25impl Entry {
26 fn apply(&self, control: &StyledElement) {
27 match self {
28 Entry::Binding { property, binding } => {
29 control.bind_binding(property, &**binding);
30 }
31 Entry::ClrPropertyValue { property, value } => {
32 let target: Ref<FerroObject> = control.to_ref().upcast();
33 if let Err(e) = property.set(&target, value(control).as_ref()) {
34 if let Some(log) = Logger::try_get(LogEventLevel::Error, LogArea::PROPERTY) {
35 let target_type = control.get_type().name();
36 log.log_with_values(
37 Some(control as &dyn Any),
38 "Error setting {Property} on {Target}: {Exception}",
39 &[&property.name(), &target_type, &e],
40 );
41 }
42 }
43 }
44 }
45 }
46}
47
48struct Pending {
49 target: WeakRef<StyledElement>,
50 entries: Vec<Entry>,
51 subscription: Option<Rc<dyn IDisposable>>,
52}
53
54ferroui_base::ferro_thread_local! {
55 static ENTRIES: RefCell<Vec<Pending>> = const { RefCell::new(Vec::new()) };
57}
58
59impl DelayedBinding {
60 pub fn add(target: &StyledElement, property: &'static FerroProperty, binding: Rc<dyn BindingBase>) {
64 if target.is_initialized() {
65 target.bind_binding(property, &*binding);
66 } else {
67 Self::add_entry(target, Entry::Binding { property, binding });
68 }
69 }
70
71 pub fn add_value(
79 target: &StyledElement,
80 property: Rc<dyn IPropertyInfo>,
81 value: impl Fn(&StyledElement) -> Option<BoxedValue> + 'static,
82 ) -> Result<(), BindingError> {
83 if target.is_initialized() {
84 let object: Ref<FerroObject> = target.to_ref().upcast();
85 property.set(&object, value(target).as_ref())
86 } else {
87 Self::add_entry(target, Entry::ClrPropertyValue { property, value: Rc::new(value) });
88 Ok(())
89 }
90 }
91
92 fn add_entry(target: &StyledElement, entry: Entry) {
93 let target_ref = target.to_ref();
94 let exists = ENTRIES.with(|entries| {
95 let mut entries = entries.borrow_mut();
96 entries.retain(|p| p.target.upgrade().is_some());
98 match entries.iter_mut().find(|p| p.target.points_to(&target_ref)) {
99 Some(pending) => {
100 pending.entries.push(entry);
101 None
102 }
103 None => Some(entry),
104 }
105 });
106
107 if let Some(entry) = exists {
108 ENTRIES.with(|entries| {
109 entries.borrow_mut().push(Pending {
110 target: target_ref.downgrade(),
111 entries: vec![entry],
112 subscription: None,
113 })
114 });
115 let weak = target_ref.downgrade();
116 let subscription = target.initialized(move || {
117 if let Some(target) = weak.upgrade() {
118 Self::apply_bindings(&target);
119 }
120 });
121 ENTRIES.with(|entries| {
122 if let Some(pending) = entries.borrow_mut().iter_mut().find(|p| p.target.points_to(&target_ref)) {
123 pending.subscription = Some(subscription);
124 }
125 });
126 }
127 }
128
129 pub fn apply_bindings(control: &StyledElement) {
131 let control_ref = control.to_ref();
132 let pending = ENTRIES.with(|entries| {
133 let mut entries = entries.borrow_mut();
134 let index = entries.iter().position(|p| p.target.points_to(&control_ref))?;
135 Some(entries.remove(index))
136 });
137
138 if let Some(pending) = pending {
139 for entry in &pending.entries {
140 entry.apply(control);
141 }
142 if let Some(subscription) = pending.subscription {
143 subscription.dispose();
144 }
145 }
146 }
147}