ferroui_xaml_interactivity/collections/
behavior_collection.rs1use crate::{IBehavior, IBehaviorEventsHandler};
4use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
5use ferroui_base::{FerroObject, Nullable, Ref, StyledElement, Visual, WeakRef};
6use ferroui_controls::{Control, TopLevel};
7use std::cell::{Cell, RefCell};
8use std::ops::Deref;
9use std::rc::{Rc, Weak};
10
11#[derive(Clone)]
18pub struct BehaviorCollection(Rc<BehaviorCollectionState>);
19
20struct BehaviorCollectionState {
21 list: FerroList<Ref<FerroObject>>,
22 associated_object: RefCell<Option<WeakRef<FerroObject>>>,
26 old_collection: RefCell<Vec<Ref<FerroObject>>>,
29 pending_synchronizations: RefCell<Vec<Ref<FerroObject>>>,
30 is_attaching_collection: Cell<bool>,
31 is_synchronizing_collection: Cell<bool>,
32 has_observed_initialized: Cell<bool>,
33 has_observed_logical_attachment: Cell<bool>,
34 has_observed_visual_attachment: Cell<bool>,
35 has_observed_loaded: Cell<bool>,
36}
37
38impl PartialEq for BehaviorCollection {
39 fn eq(&self, other: &Self) -> bool {
40 Rc::ptr_eq(&self.0, &other.0)
41 }
42}
43
44impl Deref for BehaviorCollection {
45 type Target = FerroList<Ref<FerroObject>>;
46
47 fn deref(&self) -> &FerroList<Ref<FerroObject>> {
48 &self.0.list
49 }
50}
51
52impl Default for BehaviorCollection {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl std::fmt::Debug for BehaviorCollection {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(f, "BehaviorCollection({})", self.0.list.count())
61 }
62}
63
64fn behavior_of(item: &FerroObject) -> Option<&dyn IBehavior> {
66 <dyn IBehavior>::of(item)
67}
68
69fn events_handler_of(item: &FerroObject) -> Option<&dyn IBehaviorEventsHandler> {
71 <dyn IBehaviorEventsHandler>::of(item)
72}
73
74fn is_open_top_level(associated_object: Option<&Ref<FerroObject>>) -> bool {
76 associated_object.and_then(|o| o.downcast_ref::<TopLevel>()).is_some_and(|top_level| top_level.is_loaded())
77}
78
79fn is_initialized(associated_object: Option<&Ref<FerroObject>>) -> bool {
81 associated_object.and_then(|o| o.downcast_ref::<StyledElement>()).is_some_and(StyledElement::is_initialized)
82}
83
84fn is_logically_attached(associated_object: Option<&Ref<FerroObject>>, is_open_top_level: bool) -> bool {
86 associated_object
87 .and_then(|o| o.downcast_ref::<StyledElement>())
88 .is_some_and(|styled_element| styled_element.is_attached_to_logical_tree() || is_open_top_level)
89}
90
91fn is_visually_attached(associated_object: Option<&Ref<FerroObject>>, is_open_top_level: bool) -> bool {
93 associated_object
94 .and_then(|o| o.downcast_ref::<Visual>())
95 .is_some_and(|visual| visual.is_attached_to_visual_tree() || is_open_top_level)
96}
97
98fn is_loaded(associated_object: Option<&Ref<FerroObject>>) -> bool {
100 associated_object.and_then(|o| o.downcast_ref::<Control>()).is_some_and(Control::is_loaded)
101}
102
103struct ResetFlag<'a>(&'a Cell<bool>, bool);
105
106impl Drop for ResetFlag<'_> {
107 fn drop(&mut self) {
108 self.0.set(self.1);
109 }
110}
111
112impl BehaviorCollection {
113 pub fn new() -> Self {
115 let state = Rc::new(BehaviorCollectionState {
116 list: FerroList::new(),
117 associated_object: RefCell::new(None),
118 old_collection: RefCell::new(Vec::new()),
119 pending_synchronizations: RefCell::new(Vec::new()),
120 is_attaching_collection: Cell::new(false),
121 is_synchronizing_collection: Cell::new(false),
122 has_observed_initialized: Cell::new(false),
123 has_observed_logical_attachment: Cell::new(false),
124 has_observed_visual_attachment: Cell::new(false),
125 has_observed_loaded: Cell::new(false),
126 });
127 let weak: Weak<BehaviorCollectionState> = Rc::downgrade(&state);
128 state.list.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<FerroObject>>| {
129 if let Some(state) = weak.upgrade() {
130 BehaviorCollection(state).behavior_collection_collection_changed(e);
131 }
132 }));
133 Self(state)
134 }
135
136 pub fn list(&self) -> &FerroList<Ref<FerroObject>> {
138 &self.0.list
139 }
140
141 pub fn associated_object(&self) -> Option<Ref<FerroObject>> {
143 self.0.associated_object.borrow().as_ref().and_then(WeakRef::upgrade)
144 }
145
146 fn behaviors(&self) -> Vec<Ref<FerroObject>> {
148 self.0.list.snapshot().iter().filter(|item| behavior_of(item).is_some()).cloned().collect()
149 }
150
151 pub fn attach(&self, associated_object: impl Into<Nullable<FerroObject>>) {
156 let associated_object = associated_object.into().0;
157 let current = self.associated_object();
158 if associated_object == current {
159 return;
160 }
161
162 if current.is_some() {
163 panic!("An instance of a behavior cannot be attached to more than one object at a time.");
164 }
165
166 debug_assert!(associated_object.is_some(), "The previous checks should keep us from ever setting null here.");
167 let Some(associated_object) = associated_object else {
168 return;
169 };
170 *self.0.associated_object.borrow_mut() = Some(associated_object.downgrade());
171 let behaviors = self.behaviors();
172 self.0.is_attaching_collection.set(true);
173 {
174 let _finally = ResetFlag(&self.0.is_attaching_collection, false);
175 for behavior in &behaviors {
176 let Some(associated_object_for_attach) = self.associated_object() else {
177 break;
178 };
179
180 if !self.0.list.contains(behavior) {
181 continue;
182 }
183
184 if let Some(behavior) = behavior_of(behavior) {
185 behavior.attach(Some(associated_object_for_attach));
186 }
187 }
188 }
189
190 self.synchronize_behavior_events(self.behaviors());
191 self.capture_current_lifecycle_state();
192 }
193
194 pub fn detach(&self) {
197 for item in self.0.list.snapshot().iter() {
198 if let Some(behavior_item) = behavior_of(item) {
199 if behavior_item.associated_object().is_some() {
200 behavior_item.detach();
201 }
202 }
203 }
204
205 *self.0.associated_object.borrow_mut() = None;
206 self.0.old_collection.borrow_mut().clear();
207 self.0.pending_synchronizations.borrow_mut().clear();
208 self.0.has_observed_initialized.set(false);
209 self.0.has_observed_logical_attachment.set(false);
210 self.0.has_observed_visual_attachment.set(false);
211 self.0.has_observed_loaded.set(false);
212 }
213
214 pub(crate) fn attached_to_visual_tree(&self) {
216 self.0.has_observed_visual_attachment.set(true);
217 self.dispatch_behavior_event(|handler| handler.attached_to_visual_tree_event_handler());
218 }
219
220 pub(crate) fn detached_from_visual_tree(&self) {
222 self.0.has_observed_visual_attachment.set(false);
223 self.dispatch_behavior_event(|handler| handler.detached_from_visual_tree_event_handler());
224 }
225
226 pub(crate) fn attached_to_logical_tree(&self) {
228 self.0.has_observed_logical_attachment.set(true);
229 self.dispatch_behavior_event(|handler| handler.attached_to_logical_tree_event_handler());
230 }
231
232 pub(crate) fn detached_from_logical_tree(&self) {
234 self.0.has_observed_logical_attachment.set(false);
235 self.dispatch_behavior_event(|handler| handler.detached_from_logical_tree_event_handler());
236 }
237
238 pub(crate) fn loaded(&self) {
240 self.0.has_observed_loaded.set(true);
241 self.dispatch_behavior_event(|handler| handler.loaded_event_handler());
242 }
243
244 pub(crate) fn unloaded(&self) {
246 self.0.has_observed_loaded.set(false);
247 self.dispatch_behavior_event(|handler| handler.unloaded_event_handler());
248 }
249
250 pub(crate) fn initialized(&self) {
252 self.0.has_observed_initialized.set(true);
253 self.dispatch_behavior_event(|handler| handler.initialized_event_handler());
254 }
255
256 pub(crate) fn data_context_changed(&self) {
258 self.dispatch_behavior_event(|handler| handler.data_context_changed_event_handler());
259 }
260
261 pub(crate) fn resources_changed(&self) {
263 self.dispatch_behavior_event(|handler| handler.resources_changed_event_handler());
264 }
265
266 pub(crate) fn actual_theme_variant_changed(&self) {
268 self.dispatch_behavior_event(|handler| handler.actual_theme_variant_changed_event_handler());
269 }
270
271 pub(crate) fn opened(&self) {
274 let was_synchronizing_collection = self.0.is_synchronizing_collection.replace(true);
275 let finally = FinishDispatch { collection: self, was_synchronizing_collection };
276 self.attached_to_visual_tree();
277 self.attached_to_logical_tree();
278 drop(finally);
279 }
280
281 fn dispatch_behavior_event(&self, dispatch: fn(&dyn IBehaviorEventsHandler)) {
282 let was_synchronizing_collection = self.0.is_synchronizing_collection.replace(true);
283 let finally = FinishDispatch { collection: self, was_synchronizing_collection };
284 for item in self.0.list.snapshot().iter() {
285 let (Some(behavior_events_handler), Some(behavior)) = (events_handler_of(item), behavior_of(item)) else {
286 continue;
287 };
288 if behavior.associated_object().is_some() && !self.is_pending(item) {
289 dispatch(behavior_events_handler);
290 }
291 }
292 drop(finally);
293 }
294
295 fn finish_dispatch(&self, was_synchronizing_collection: bool) {
298 self.0.is_synchronizing_collection.set(was_synchronizing_collection);
299 if !was_synchronizing_collection && !self.0.pending_synchronizations.borrow().is_empty() {
300 let pending = std::mem::take(&mut *self.0.pending_synchronizations.borrow_mut());
301 self.synchronize_behavior_events(pending);
302 }
303 }
304
305 fn is_pending(&self, behavior: &Ref<FerroObject>) -> bool {
306 self.0.pending_synchronizations.borrow().iter().any(|pending| pending == behavior)
307 }
308
309 fn behavior_collection_collection_changed(&self, event_args: &NotifyCollectionChangedEventArgs<'_, Ref<FerroObject>>) {
310 if event_args.action == NotifyCollectionChangedAction::Reset {
311 let old_collection = std::mem::take(&mut *self.0.old_collection.borrow_mut());
312 for behavior in &old_collection {
313 if let Some(behavior) = behavior_of(behavior) {
314 if behavior.associated_object().is_some() {
315 behavior.detach();
316 }
317 }
318 }
319
320 let mut attached_behaviors = Vec::with_capacity(self.0.list.count());
321 for new_item in self.0.list.snapshot().iter() {
322 self.verified_attach(new_item);
323 self.0.old_collection.borrow_mut().push(new_item.clone());
324 attached_behaviors.push(new_item.clone());
325 }
326
327 if !self.0.is_attaching_collection.get() {
328 self.queue_or_synchronize_behavior_events_many(attached_behaviors);
329 }
330 #[cfg(debug_assertions)]
331 self.verify_old_collection_integrity();
332 return;
333 }
334
335 match event_args.action {
336 NotifyCollectionChangedAction::Add => {
337 let event_index = event_args.new_starting_index.max(0) as usize;
338 let Some(changed_item) = event_args.new_items.first().cloned() else {
339 return;
340 };
341 self.verified_attach(&changed_item);
342 {
343 let mut old_collection = self.0.old_collection.borrow_mut();
344 let index = event_index.min(old_collection.len());
345 old_collection.insert(index, changed_item.clone());
346 }
347 if !self.0.is_attaching_collection.get() {
348 self.queue_or_synchronize_behavior_events(changed_item);
349 }
350 }
351
352 NotifyCollectionChangedAction::Replace => {
353 let event_index = event_args.old_starting_index;
354 let event_index = if event_index == -1 { 0 } else { event_index as usize };
355
356 let Some(changed_item) = event_args.new_items.first().cloned() else {
357 return;
358 };
359
360 let old_item = self.0.old_collection.borrow()[event_index].clone();
361 if let Some(old_item) = behavior_of(&old_item) {
362 if old_item.associated_object().is_some() {
363 old_item.detach();
364 }
365 }
366
367 self.verified_attach(&changed_item);
368 self.0.old_collection.borrow_mut()[event_index] = changed_item.clone();
369 if !self.0.is_attaching_collection.get() {
370 self.queue_or_synchronize_behavior_events(changed_item);
371 }
372 }
373
374 NotifyCollectionChangedAction::Remove => {
375 let event_index = event_args.old_starting_index.max(0) as usize;
376
377 let old_item = self.0.old_collection.borrow()[event_index].clone();
378 if let Some(old_item) = behavior_of(&old_item) {
379 if old_item.associated_object().is_some() {
380 old_item.detach();
381 }
382 }
383
384 self.0.old_collection.borrow_mut().remove(event_index);
385 }
386
387 _ => {
388 debug_assert!(false, "Unsupported collection operation attempted.");
389 }
390 }
391 #[cfg(debug_assertions)]
392 self.verify_old_collection_integrity();
393 }
394
395 fn verified_attach(&self, item: &Ref<FerroObject>) {
396 let Some(behavior) = behavior_of(item) else {
397 panic!("Only IBehavior types are supported in a BehaviorCollection.");
398 };
399
400 if self.0.old_collection.borrow().iter().any(|old| old == item) {
401 panic!("Cannot add an instance of a behavior to a BehaviorCollection more than once.");
402 }
403
404 if let Some(associated_object) = self.associated_object() {
405 behavior.attach(Some(associated_object));
406 }
407 }
408
409 fn queue_or_synchronize_behavior_events(&self, behavior: Ref<FerroObject>) {
410 if self.0.is_synchronizing_collection.get() || self.has_pending_host_lifecycle_event() {
411 self.queue_pending_synchronization(behavior);
412 return;
413 }
414
415 self.synchronize_behavior_events(vec![behavior]);
416 }
417
418 fn queue_or_synchronize_behavior_events_many(&self, behaviors: Vec<Ref<FerroObject>>) {
419 if self.0.is_synchronizing_collection.get() || self.has_pending_host_lifecycle_event() {
420 for behavior in behaviors {
421 self.queue_pending_synchronization(behavior);
422 }
423
424 return;
425 }
426
427 self.synchronize_behavior_events(behaviors);
428 }
429
430 fn queue_pending_synchronization(&self, behavior: Ref<FerroObject>) {
431 if !self.is_pending(&behavior) {
432 self.0.pending_synchronizations.borrow_mut().push(behavior);
433 }
434 }
435
436 fn has_pending_host_lifecycle_event(&self) -> bool {
437 let associated_object = self.associated_object();
438 let Some(associated) = associated_object.as_ref() else {
439 return false;
440 };
441 let associated_object = Some(associated);
442
443 if !self.0.has_observed_initialized.get() && is_initialized(associated_object) {
444 return true;
445 }
446
447 let is_open_top_level = is_open_top_level(associated_object);
448 if !self.0.has_observed_logical_attachment.get() && is_logically_attached(associated_object, is_open_top_level) {
449 return true;
450 }
451
452 if !self.0.has_observed_visual_attachment.get() && is_visually_attached(associated_object, is_open_top_level) {
453 return true;
454 }
455
456 !self.0.has_observed_loaded.get() && is_loaded(associated_object)
457 }
458
459 fn capture_current_lifecycle_state(&self) {
460 let associated_object = self.associated_object();
461 let associated_object = associated_object.as_ref();
462 let is_open_top_level = is_open_top_level(associated_object);
463 self.0.has_observed_initialized.set(is_initialized(associated_object));
464 self.0.has_observed_logical_attachment.set(is_logically_attached(associated_object, is_open_top_level));
465 self.0.has_observed_visual_attachment.set(is_visually_attached(associated_object, is_open_top_level));
466 self.0.has_observed_loaded.set(is_loaded(associated_object));
467 }
468
469 fn synchronize_behavior_events(&self, behaviors: Vec<Ref<FerroObject>>) {
470 self.0.is_synchronizing_collection.set(true);
471 let _finally = FinishSynchronization(self);
472 let mut current_batch = behaviors;
473 for phase in 0..4 {
474 let mut index = 0;
475 while index < current_batch.len() {
476 let behavior = current_batch[index].clone();
477 Self::synchronize_phase(&behavior, phase);
478 self.drain_pending_synchronizations(&mut current_batch, phase);
479 index += 1;
480 }
481 }
482 }
483
484 fn drain_pending_synchronizations(&self, current_batch: &mut Vec<Ref<FerroObject>>, current_phase: i32) {
485 let mut catch_up_batch: Vec<(Ref<FerroObject>, i32)> = Vec::new();
486 loop {
487 if !self.0.pending_synchronizations.borrow().is_empty() {
488 let pending = std::mem::take(&mut *self.0.pending_synchronizations.borrow_mut());
489 for behavior in pending {
490 if !current_batch.contains(&behavior) {
491 current_batch.push(behavior.clone());
492 }
493
494 let is_already_queued = catch_up_batch.iter().any(|(queued, _)| queued.ptr_eq(&behavior));
495 if !is_already_queued {
496 catch_up_batch.push((behavior, 0));
497 }
498 }
499 }
500
501 let mut next_index: Option<usize> = None;
502 let mut next_phase = i32::MAX;
503 for (index, (_, candidate_phase)) in catch_up_batch.iter().enumerate() {
504 if *candidate_phase <= current_phase && *candidate_phase < next_phase {
505 next_index = Some(index);
506 next_phase = *candidate_phase;
507 }
508 }
509
510 let Some(next_index) = next_index else {
511 return;
512 };
513
514 let (behavior, phase) = catch_up_batch[next_index].clone();
515 Self::synchronize_phase(&behavior, phase);
516 catch_up_batch[next_index] = (behavior, phase + 1);
517 }
518 }
519
520 fn synchronize_phase(behavior: &Ref<FerroObject>, phase: i32) {
521 match phase {
522 0 => Self::synchronize_initialized(behavior),
523 1 => Self::synchronize_logical_attachment(behavior),
524 2 => Self::synchronize_visual_attachment(behavior),
525 3 => Self::synchronize_loaded(behavior),
526 _ => {}
527 }
528 }
529
530 fn synchronize_initialized(behavior: &Ref<FerroObject>) {
531 let (Some(events_handler), Some(behavior)) = (events_handler_of(behavior), behavior_of(behavior)) else {
532 return;
533 };
534 if is_initialized(behavior.associated_object().as_ref()) {
535 events_handler.initialized_event_handler();
536 }
537 }
538
539 fn synchronize_logical_attachment(behavior: &Ref<FerroObject>) {
540 let (Some(events_handler), Some(behavior)) = (events_handler_of(behavior), behavior_of(behavior)) else {
541 return;
542 };
543 let associated_object = behavior.associated_object();
544 let is_open_top_level = is_open_top_level(associated_object.as_ref());
545 if is_logically_attached(associated_object.as_ref(), is_open_top_level) {
546 events_handler.attached_to_logical_tree_event_handler();
547 }
548 }
549
550 fn synchronize_visual_attachment(behavior: &Ref<FerroObject>) {
551 let (Some(events_handler), Some(behavior)) = (events_handler_of(behavior), behavior_of(behavior)) else {
552 return;
553 };
554 let associated_object = behavior.associated_object();
555 let is_open_top_level = is_open_top_level(associated_object.as_ref());
556 if is_visually_attached(associated_object.as_ref(), is_open_top_level) {
557 events_handler.attached_to_visual_tree_event_handler();
558 }
559 }
560
561 fn synchronize_loaded(behavior: &Ref<FerroObject>) {
562 let (Some(events_handler), Some(behavior)) = (events_handler_of(behavior), behavior_of(behavior)) else {
563 return;
564 };
565 if is_loaded(behavior.associated_object().as_ref()) {
566 events_handler.loaded_event_handler();
567 }
568 }
569
570 #[cfg(debug_assertions)]
571 fn verify_old_collection_integrity(&self) {
572 let items = self.0.list.snapshot();
573 let old_collection = self.0.old_collection.borrow();
574 let is_valid = items.len() == old_collection.len() && items.iter().zip(old_collection.iter()).all(|(a, b)| a == b);
575
576 debug_assert!(is_valid, "Referential integrity of the collection has been compromised.");
577 }
578}
579
580struct FinishDispatch<'a> {
583 collection: &'a BehaviorCollection,
584 was_synchronizing_collection: bool,
585}
586
587impl Drop for FinishDispatch<'_> {
588 fn drop(&mut self) {
589 if std::thread::panicking() {
590 self.collection.0.is_synchronizing_collection.set(self.was_synchronizing_collection);
591 return;
592 }
593 self.collection.finish_dispatch(self.was_synchronizing_collection);
594 }
595}
596
597struct FinishSynchronization<'a>(&'a BehaviorCollection);
599
600impl Drop for FinishSynchronization<'_> {
601 fn drop(&mut self) {
602 (self.0).0.is_synchronizing_collection.set(false);
603 let pending = std::mem::take(&mut *(self.0).0.pending_synchronizations.borrow_mut());
604 drop(pending);
605 }
606}