Skip to main content

ferroui_base/visual_tree/
visual_locator.rs

1use crate::reactive::{IDisposable, IObservable, IObserver, LightweightObservable, LightweightObservableBase};
2use crate::{Ref, TypeInfo, Visual, WeakRef};
3use std::cell::RefCell;
4use std::rc::{Rc, Weak};
5
6/// Tracks an ancestor of a visual in the visual tree.
7pub struct VisualLocator;
8
9impl VisualLocator {
10    /// An observable of the ancestor at `ancestor_level` (counting only
11    /// ancestors of `ancestor_type`, when given) of `relative_to`, or `None`
12    /// while `relative_to` is not attached to a visual tree. It publishes
13    /// the ancestor again whenever `relative_to` is attached or detached.
14    // Deviation (DEVIATIONS.md, Visual tree): upstream takes an `int` level; a negative level
15    // cannot be passed here.
16    pub fn track(
17        relative_to: &Visual,
18        ancestor_level: usize,
19        ancestor_type: Option<&'static TypeInfo>,
20    ) -> Rc<dyn IObservable<Option<Ref<Visual>>>> {
21        VisualTracker::new(relative_to.to_ref(), ancestor_level, ancestor_type)
22    }
23}
24
25struct VisualTracker {
26    this: Weak<VisualTracker>,
27    base: LightweightObservableBase<Option<Ref<Visual>>>,
28    /// Held weakly (DEVIATIONS.md, Logical tree): the tracker belongs to a
29    /// binding of the visual; held strongly, as the managed original holds
30    /// it, the visual keeps itself alive.
31    relative_to: WeakRef<Visual>,
32    ancestor_level: usize,
33    ancestor_type: Option<&'static TypeInfo>,
34    subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
35}
36
37impl VisualTracker {
38    fn new(relative_to: Ref<Visual>, ancestor_level: usize, ancestor_type: Option<&'static TypeInfo>) -> Rc<Self> {
39        Rc::new_cyclic(|this| Self {
40            this: this.clone(),
41            base: LightweightObservableBase::new(),
42            relative_to: relative_to.downgrade(),
43            ancestor_level,
44            ancestor_type,
45            subscriptions: RefCell::new(Vec::new()),
46        })
47    }
48
49    fn attached_detached(&self) {
50        self.base.publish_next(self.get_result());
51    }
52
53    fn get_result(&self) -> Option<Ref<Visual>> {
54        let relative_to = self.relative_to.upgrade()?;
55        if relative_to.is_attached_to_visual_tree() {
56            relative_to
57                .get_visual_ancestors()
58                .filter(|x| self.ancestor_type.is_none_or(|t| t.is_assignable_from(x.get_type())))
59                .nth(self.ancestor_level)
60        } else {
61            None
62        }
63    }
64}
65
66impl LightweightObservable<Option<Ref<Visual>>> for VisualTracker {
67    fn observable_base(&self) -> &LightweightObservableBase<Option<Ref<Visual>>> {
68        &self.base
69    }
70
71    fn initialize(&self) {
72        let this = self.this.clone();
73        let Some(relative_to) = self.relative_to.upgrade() else { return };
74        let attached = relative_to.attached_to_visual_tree(move |_| {
75            if let Some(this) = this.upgrade() {
76                this.attached_detached();
77            }
78        });
79        let this = self.this.clone();
80        let detached = relative_to.detached_from_visual_tree(move |_| {
81            if let Some(this) = this.upgrade() {
82                this.attached_detached();
83            }
84        });
85        self.subscriptions.borrow_mut().extend([attached, detached]);
86    }
87
88    fn deinitialize(&self) {
89        let subscriptions = std::mem::take(&mut *self.subscriptions.borrow_mut());
90        for subscription in subscriptions {
91            subscription.dispose();
92        }
93    }
94
95    fn subscribed(&self, observer: &Rc<dyn IObserver<Option<Ref<Visual>>>>, _first: bool) {
96        observer.on_next(self.get_result());
97    }
98}
99
100impl IObservable<Option<Ref<Visual>>> for VisualTracker {
101    fn subscribe(&self, observer: Rc<dyn IObserver<Option<Ref<Visual>>>>) -> Rc<dyn IDisposable> {
102        let this = self.this.upgrade().expect("the tracker is alive while it is subscribed to");
103        LightweightObservableBase::subscribe(&this, observer)
104    }
105}