Skip to main content

ferroui_base/input/text_input/
transform_tracking_helper.rs

1use crate::media::MediaContext;
2use crate::reactive::{Disposable, IDisposable, IObservable, Observable};
3use crate::threading::{Dispatcher, DispatcherPriority};
4use crate::utilities::HandlerList;
5use crate::{FerroPropertyChangedEventArgs, Matrix, Ref, Visual, WeakRef};
6use std::cell::{Cell, RefCell};
7use std::rc::{Rc, Weak};
8
9/// Tracks the transform of a visual to the root of its visual tree: tells
10/// when it changes because the bounds of the visual or of one of its
11/// ancestors changed, or because the visual was attached or detached.
12///
13/// The helper does not keep the tracked visual alive; the visual keeps the
14/// helper alive for as long as it is tracked.
15pub struct TransformTrackingHelper {
16    this: Weak<TransformTrackingHelper>,
17    defer_after_render_pass: bool,
18    visual: RefCell<Option<WeakRef<Visual>>>,
19    queued_for_update: Cell<bool>,
20    attachment_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
21    property_changed_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
22    matrix: Cell<Option<Matrix>>,
23    matrix_changed: HandlerList<dyn Fn()>,
24}
25
26impl TransformTrackingHelper {
27    /// Creates the helper. With `defer_after_render_pass` a change of
28    /// bounds is processed after the next render pass, otherwise right
29    /// before it.
30    pub fn new(defer_after_render_pass: bool) -> Rc<TransformTrackingHelper> {
31        Rc::new_cyclic(|this| TransformTrackingHelper {
32            this: this.clone(),
33            defer_after_render_pass,
34            visual: RefCell::new(None),
35            queued_for_update: Cell::new(false),
36            attachment_subscriptions: RefCell::new(Vec::new()),
37            property_changed_subscriptions: RefCell::new(Vec::new()),
38            matrix: Cell::new(None),
39            matrix_changed: HandlerList::new(),
40        })
41    }
42
43    fn strong(&self) -> Rc<TransformTrackingHelper> {
44        self.this.upgrade().expect("the helper is alive while it is used")
45    }
46
47    fn visual(&self) -> Option<Ref<Visual>> {
48        self.visual.borrow().as_ref().and_then(WeakRef::upgrade)
49    }
50
51    /// Sets the visual to track, or stops tracking.
52    pub fn set_visual(&self, visual: Option<&Ref<Visual>>) {
53        self.dispose();
54        *self.visual.borrow_mut() = visual.map(Ref::downgrade);
55
56        if let Some(visual) = visual {
57            let attached = {
58                let this = self.strong();
59                visual.attached_to_visual_tree(move |_| this.on_attached_to_visual_tree())
60            };
61            let detached = {
62                let this = self.strong();
63                visual.detached_from_visual_tree(move |_| this.on_detached_from_visual_tree())
64            };
65            self.attachment_subscriptions.borrow_mut().extend([attached, detached]);
66
67            if visual.is_attached_to_visual_tree() {
68                self.subscribe_to_parents();
69            }
70            self.update_matrix();
71        }
72    }
73
74    /// The transform of the tracked visual to its visual root, if it is
75    /// attached to one.
76    pub fn matrix(&self) -> Option<Matrix> {
77        self.matrix.get()
78    }
79
80    /// Raised when [`matrix`](Self::matrix) has changed. Disposing the
81    /// returned handle unsubscribes.
82    pub fn matrix_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
83        let token = self.matrix_changed.add(Rc::new(handler));
84        let weak = self.this.clone();
85        Disposable::create(move || {
86            if let Some(this) = weak.upgrade() {
87                this.matrix_changed.remove(token);
88            }
89        })
90    }
91
92    /// Stops tracking the visual.
93    pub fn dispose(&self) {
94        if self.visual.borrow().is_none() {
95            return;
96        }
97
98        self.unsubscribe_from_parents();
99        let subscriptions = std::mem::take(&mut *self.attachment_subscriptions.borrow_mut());
100        for subscription in subscriptions {
101            subscription.dispose();
102        }
103        *self.visual.borrow_mut() = None;
104    }
105
106    fn subscribe_to_parents(&self) {
107        let mut visual = self.visual();
108
109        while let Some(v) = visual {
110            let this = self.strong();
111            let subscription = v.property_changed(move |e| this.property_changed_handler(e));
112            self.property_changed_subscriptions.borrow_mut().push(subscription);
113            visual = v.visual_parent();
114        }
115    }
116
117    fn unsubscribe_from_parents(&self) {
118        let subscriptions = std::mem::take(&mut *self.property_changed_subscriptions.borrow_mut());
119        for subscription in subscriptions {
120            subscription.dispose();
121        }
122    }
123
124    fn update_matrix(&self) {
125        self.queued_for_update.set(false);
126        let mut matrix = None;
127
128        if let Some(visual) = self.visual() {
129            if let Some(root) = visual.visual_root() {
130                matrix = visual.transform_to_visual(&root);
131            }
132        }
133
134        if self.matrix.get() != matrix {
135            self.matrix.set(matrix);
136
137            if !self.matrix_changed.is_empty() {
138                for (_, handler) in self.matrix_changed.snapshot().iter() {
139                    handler();
140                }
141            }
142        }
143    }
144
145    fn on_attached_to_visual_tree(&self) {
146        self.subscribe_to_parents();
147        self.update_matrix();
148    }
149
150    fn enqueue_for_update(&self) {
151        if self.queued_for_update.get() {
152            return;
153        }
154        self.queued_for_update.set(true);
155
156        let this = self.strong();
157        if self.defer_after_render_pass {
158            Dispatcher::ui_thread().post_local(move || this.update_matrix(), DispatcherPriority::AFTER_RENDER);
159        } else {
160            MediaContext::instance().begin_invoke_on_render(Rc::new(move || this.update_matrix()));
161        }
162    }
163
164    fn property_changed_handler(&self, e: &FerroPropertyChangedEventArgs<'_>) {
165        if e.is_effective_value_change() && e.property() == Visual::bounds_property().as_property() {
166            self.enqueue_for_update();
167        }
168    }
169
170    fn on_detached_from_visual_tree(&self) {
171        self.unsubscribe_from_parents();
172        self.update_matrix();
173    }
174
175    /// Tracks the transform of a visual, calling `cb` with the visual and
176    /// its transform whenever it changes. Disposing the returned handle
177    /// stops tracking.
178    pub fn track(
179        visual: &Ref<Visual>,
180        defer_after_render_pass: bool,
181        cb: impl Fn(&Ref<Visual>, Option<Matrix>) + 'static,
182    ) -> Rc<dyn IDisposable> {
183        let rv = TransformTrackingHelper::new(defer_after_render_pass);
184        {
185            let weak = Rc::downgrade(&rv);
186            let visual = visual.downgrade();
187            rv.matrix_changed(move || {
188                if let (Some(rv), Some(visual)) = (weak.upgrade(), visual.upgrade()) {
189                    cb(&visual, rv.matrix());
190                }
191            });
192        }
193        rv.set_visual(Some(visual));
194        rv
195    }
196
197    /// Returns an observable of the transform of a visual to its visual
198    /// root.
199    pub fn observe(visual: &Ref<Visual>, defer_after_render_pass: bool) -> Rc<dyn IObservable<Option<Matrix>>> {
200        let visual = visual.downgrade();
201        Observable::create(move |observer| {
202            let rv = TransformTrackingHelper::new(defer_after_render_pass);
203            {
204                let weak = Rc::downgrade(&rv);
205                rv.matrix_changed(move || {
206                    if let Some(rv) = weak.upgrade() {
207                        observer.on_next(rv.matrix());
208                    }
209                });
210            }
211            rv.set_visual(visual.upgrade().as_ref());
212            rv as Rc<dyn IDisposable>
213        })
214    }
215}
216
217impl IDisposable for TransformTrackingHelper {
218    fn dispose(&self) {
219        TransformTrackingHelper::dispose(self)
220    }
221}