ferroui_xaml_interactivity/styled_element/
styled_element_behavior.rs1use crate::helpers::TemplatedParentHelper;
4use crate::{DisposableAction, IBehavior, IBehaviorEventsHandler};
5use ferroui_base::reactive::{IDisposable, ObservableExt};
6use ferroui_base::{
7 ferro_class, ferro_properties, BoxedValue, ElementRef, FerroObject, FerroObjectExtensions, FerroObjectImpl,
8 FerroProperty, Nullable, Ref, StyledElement, StyledElementImpl, StyledProperty, WeakRef,
9};
10use ferroui_controls::primitives::FlyoutBase;
11use ferroui_controls::{Control, TopLevel};
12use std::cell::{Cell, RefCell};
13use std::rc::Rc;
14
15#[repr(C)]
25pub struct StyledElementBehavior {
26 base: StyledElement,
27 data_context_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
28 associated_object: RefCell<Option<WeakRef<FerroObject>>>,
31 is_attached_to_logical_tree: Cell<bool>,
32 is_attached_to_visual_tree: Cell<bool>,
33 is_initialized_notified: Cell<bool>,
34 is_loaded: Cell<bool>,
35}
36
37ferro_class! {
38 StyledElementBehavior: StyledElement, virtuals StyledElementBehaviorImpl: StyledElementImpl {
39 fn on_attached(this);
43 fn on_detaching(this);
47 fn on_attached_to_visual_tree(this);
51 fn on_detached_from_visual_tree(this);
55 fn on_attached_to_logical_tree_event(this);
60 fn on_detached_from_logical_tree_event(this);
65 fn on_loaded(this);
69 fn on_unloaded(this);
73 fn on_initialized_event(this);
77 fn on_data_context_changed_event(this);
81 fn on_resources_changed_event(this);
85 fn on_actual_theme_variant_changed_event(this);
89 #[doc(hidden)]
91 fn initialize(this);
92 #[doc(hidden)]
94 fn attach_behavior_to_logical_tree(this);
95 #[doc(hidden)]
97 fn detach_behavior_from_logical_tree(this);
98 }
99}
100
101ferroui_base::ferro_impl_classes!(StyledElementBehavior: FerroObjectImpl, StyledElementImpl);
102
103impl StyledElementBehaviorImpl for StyledElementBehavior {
104 fn on_attached(_this: &Self) {}
105
106 fn on_detaching(_this: &Self) {}
107
108 fn on_attached_to_visual_tree(_this: &Self) {}
109
110 fn on_detached_from_visual_tree(_this: &Self) {}
111
112 fn on_attached_to_logical_tree_event(_this: &Self) {}
113
114 fn on_detached_from_logical_tree_event(_this: &Self) {}
115
116 fn on_loaded(_this: &Self) {}
117
118 fn on_unloaded(_this: &Self) {}
119
120 fn on_initialized_event(_this: &Self) {}
121
122 fn on_data_context_changed_event(_this: &Self) {}
123
124 fn on_resources_changed_event(_this: &Self) {}
125
126 fn on_actual_theme_variant_changed_event(_this: &Self) {}
127
128 fn initialize(this: &Self) {
129 this.initialize_if_needed();
130 }
131
132 fn attach_behavior_to_logical_tree(this: &Self) {
133 let mut parent: Option<Ref<StyledElement>> = None;
134 let mut templated_parent: Option<Ref<FerroObject>> = None;
135
136 if let Some(associated_object) = this.associated_object() {
137 if let Some(top_level) = associated_object.cast::<TopLevel>() {
138 templated_parent = top_level.templated_parent();
139 parent = Some(top_level.upcast());
140 } else if let Some(flyout) = associated_object.downcast_ref::<FlyoutBase>() {
141 if let Some(target) = flyout.target() {
142 templated_parent = target.templated_parent();
143 parent = Some(target.upcast());
144 }
145 } else if let Some(styled_element) = associated_object.cast::<StyledElement>() {
146 if styled_element.parent().is_some() {
147 templated_parent = styled_element.templated_parent();
148 parent = Some(styled_element);
149 }
150 }
151 }
152
153 let Some(parent) = parent else {
154 return;
155 };
156
157 this.set_parent(&parent);
159
160 if templated_parent.is_some() {
162 TemplatedParentHelper::set_templated_parent(this, templated_parent);
163 }
164 }
165
166 fn detach_behavior_from_logical_tree(this: &Self) {
167 this.set_parent(None);
168
169 if this.templated_parent().is_some() {
170 TemplatedParentHelper::set_templated_parent(this, None);
171 }
172 }
173}
174
175ferro_properties! {
176 impl StyledElementBehavior {
177 pub fn is_enabled_property() -> StyledProperty<bool> {
179 FerroProperty::register::<StyledElementBehavior, _>("IsEnabled", true)
180 }
181 }
182}
183
184struct DetachFromLogicalTree<'a>(&'a StyledElementBehavior);
187
188impl Drop for DetachFromLogicalTree<'_> {
189 fn drop(&mut self) {
190 if !self.0.associated_object().is_some_and(|associated_object| associated_object.is::<TopLevel>()) {
191 self.0.detach_behavior_from_logical_tree();
192 }
193 }
194}
195
196impl StyledElementBehavior {
197 fn static_constructor() {
199 <dyn IBehavior>::register::<StyledElementBehavior>();
200 <dyn IBehaviorEventsHandler>::register::<StyledElementBehavior>();
201 }
202
203 pub fn construct() -> Self {
205 Self {
206 base: StyledElement::construct(),
207 data_context_disposable: RefCell::new(None),
208 associated_object: RefCell::new(None),
209 is_attached_to_logical_tree: Cell::new(false),
210 is_attached_to_visual_tree: Cell::new(false),
211 is_initialized_notified: Cell::new(false),
212 is_loaded: Cell::new(false),
213 }
214 }
215
216 pub fn associated_object(&self) -> Option<Ref<FerroObject>> {
218 self.associated_object.borrow().as_ref().and_then(WeakRef::upgrade)
219 }
220
221 pub fn associated_styled_element(&self) -> Option<Ref<StyledElement>> {
223 self.associated_object().and_then(|associated_object| associated_object.cast::<StyledElement>())
224 }
225
226 pub fn is_enabled(&self) -> bool {
228 self.get_value(Self::is_enabled_property())
229 }
230
231 pub fn set_is_enabled(&self, value: bool) {
232 self.set_value(Self::is_enabled_property(), value)
233 }
234
235 pub fn attach(&self, associated_object: impl Into<Nullable<FerroObject>>) {
240 let associated_object = associated_object.into().0;
241 let current = self.associated_object();
242 if associated_object == current {
243 return;
244 }
245
246 if current.is_some() {
247 panic!("An instance of a behavior cannot be attached to more than one object at a time.");
248 }
249
250 debug_assert!(associated_object.is_some(), "Cannot attach the behavior to a null object.");
251 let Some(associated_object) = associated_object else {
252 panic!("Value cannot be null. (Parameter 'associatedObject')");
253 };
254 *self.associated_object.borrow_mut() = Some(associated_object.downgrade());
255 let data_context_disposable = self.synchronize_data_context(&associated_object);
256 *self.data_context_disposable.borrow_mut() = data_context_disposable;
257
258 if associated_object.is::<TopLevel>() {
261 self.attach_behavior_to_logical_tree();
262 }
263
264 self.on_attached();
265 }
266
267 pub fn detach(&self) {
269 self.on_detaching();
270
271 if self.parent().is_some() || self.templated_parent().is_some() {
272 self.detach_behavior_from_logical_tree();
273 }
274
275 let data_context_disposable = self.data_context_disposable.borrow_mut().take();
276 if let Some(data_context_disposable) = data_context_disposable {
277 data_context_disposable.dispose();
278 }
279 self.is_attached_to_logical_tree.set(false);
280 self.is_attached_to_visual_tree.set(false);
281 self.is_initialized_notified.set(false);
282 self.is_loaded.set(false);
283 *self.associated_object.borrow_mut() = None;
284 }
285
286 fn synchronize_data_context(&self, associated_object: &Ref<FerroObject>) -> Option<Rc<dyn IDisposable>> {
287 let this = self.to_ref().downgrade();
288 let data_context_property = StyledElement::data_context_property();
289
290 if let Some(styled_element) = associated_object.downcast_ref::<StyledElement>() {
291 return Some(styled_element.get_observable(data_context_property).subscribe_fn(
297 move |x: Option<BoxedValue>| {
298 if let Some(this) = this.upgrade() {
299 this.set_current_value(data_context_property, x);
300 }
301 },
302 ));
303 }
304
305 if let Some(flyout) = associated_object.downcast_ref::<FlyoutBase>() {
306 let target_data_context_subscription: Rc<RefCell<Option<Rc<dyn IDisposable>>>> = Rc::new(RefCell::new(None));
307
308 let subscription = target_data_context_subscription.clone();
309 let target_changed = move |target: Option<Ref<Control>>| {
310 let Some(this) = this.upgrade() else {
311 return;
312 };
313
314 let previous = subscription.borrow_mut().take();
315 if let Some(previous) = previous {
316 previous.dispose();
317 }
318
319 if this.parent().is_some() || this.templated_parent().is_some() {
320 this.detach_behavior_from_logical_tree();
321 }
322
323 let Some(target) = target else {
324 this.set_current_value(data_context_property, None);
325 return;
326 };
327
328 this.attach_behavior_to_logical_tree();
329 let behavior = this.downgrade();
330 let target_object: &FerroObject = ⌖
331 let target_subscription = FerroObjectExtensions::get_observable(target_object, data_context_property)
332 .subscribe_fn(move |x: Option<BoxedValue>| {
333 if let Some(behavior) = behavior.upgrade() {
334 behavior.set_current_value(data_context_property, x);
335 }
336 });
337 *subscription.borrow_mut() = Some(target_subscription);
338 };
339
340 let target_subscription = flyout
341 .get_observable(FlyoutBase::target_property())
342 .subscribe_fn(move |target: Option<ElementRef<Control>>| target_changed(ElementRef::resolve(&target)));
343
344 return Some(DisposableAction::create(move || {
345 target_subscription.dispose();
346 let target_data_context = target_data_context_subscription.borrow().clone();
347 if let Some(target_data_context) = target_data_context {
348 target_data_context.dispose();
349 }
350 }));
351 }
352
353 None
354 }
355}
356
357impl IBehavior for StyledElementBehavior {
358 fn associated_object(&self) -> Option<Ref<FerroObject>> {
359 StyledElementBehavior::associated_object(self)
360 }
361
362 fn attach(&self, associated_object: Option<Ref<FerroObject>>) {
363 StyledElementBehavior::attach(self, associated_object)
364 }
365
366 fn detach(&self) {
367 StyledElementBehavior::detach(self)
368 }
369}
370
371impl IBehaviorEventsHandler for StyledElementBehavior {
372 fn attached_to_visual_tree_event_handler(&self) {
373 if self.is_attached_to_visual_tree.get() {
374 return;
375 }
376
377 self.is_attached_to_visual_tree.set(true);
378 self.attach_behavior_to_logical_tree();
379
380 self.on_attached_to_visual_tree();
381 }
382
383 fn detached_from_visual_tree_event_handler(&self) {
384 if !self.is_attached_to_visual_tree.get() {
385 return;
386 }
387
388 self.is_attached_to_visual_tree.set(false);
389 let _finally = DetachFromLogicalTree(self);
390 self.on_detached_from_visual_tree();
391 }
392
393 fn attached_to_logical_tree_event_handler(&self) {
394 if self.is_attached_to_logical_tree.get() {
395 return;
396 }
397
398 self.is_attached_to_logical_tree.set(true);
399 self.attach_behavior_to_logical_tree();
400
401 self.on_attached_to_logical_tree_event();
402 }
403
404 fn detached_from_logical_tree_event_handler(&self) {
405 if !self.is_attached_to_logical_tree.get() {
406 return;
407 }
408
409 self.is_attached_to_logical_tree.set(false);
410 let _finally = DetachFromLogicalTree(self);
411 self.on_detached_from_logical_tree_event();
412 }
413
414 fn loaded_event_handler(&self) {
415 if self.is_loaded.get() {
416 return;
417 }
418
419 self.is_loaded.set(true);
420 self.on_loaded();
421 }
422
423 fn unloaded_event_handler(&self) {
424 if !self.is_loaded.get() {
425 return;
426 }
427
428 self.is_loaded.set(false);
429 self.on_unloaded();
430 }
431
432 fn initialized_event_handler(&self) {
433 if self.is_initialized_notified.get() {
434 return;
435 }
436
437 self.is_initialized_notified.set(true);
438 self.initialize();
439
440 self.on_initialized_event();
441 }
442
443 fn data_context_changed_event_handler(&self) {
444 self.on_data_context_changed_event();
445 }
446
447 fn resources_changed_event_handler(&self) {
448 self.on_resources_changed_event();
449 }
450
451 fn actual_theme_variant_changed_event_handler(&self) {
452 self.on_actual_theme_variant_changed_event();
453 }
454}