ferroui_base/input/text_input/
transform_tracking_helper.rs1use 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
9pub 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 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 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 pub fn matrix(&self) -> Option<Matrix> {
77 self.matrix.get()
78 }
79
80 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 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 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 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}