ferroui_xaml_interactivity/
interaction.rs1use crate::{ActionCollection, BehaviorCollection, IAction};
4use ferroui_base::reactive::{Disposable, IDisposable};
5use ferroui_base::threading::{Dispatcher, DispatcherPriority};
6use ferroui_base::{
7 ferro_properties, ferro_static_type, AttachedProperty, BoxedValue, FerroObject, FerroProperty,
8 FerroPropertyChangedEventArgs, Ref, StyledElement, Visual, WeakRef,
9};
10use ferroui_controls::{Control, TopLevel};
11use std::cell::RefCell;
12use std::rc::Rc;
13
14pub struct Interaction;
17
18ferro_static_type!(Interaction);
19
20ferro_properties! {
21 impl Interaction {
22 pub fn behaviors_property() -> AttachedProperty<Option<BehaviorCollection>> {
24 let property = FerroProperty::register_attached::<Interaction, FerroObject, Option<BehaviorCollection>>(
25 "Behaviors",
26 None,
27 );
28 let _ = property.changed().subscribe(Interaction::behaviors_changed);
30 property
31 }
32 }
33}
34
35struct InteractionHostHandlers;
42
43ferro_static_type!(InteractionHostHandlers);
44
45ferro_properties! {
46 impl InteractionHostHandlers {
47 fn host_handlers_property() -> AttachedProperty<Option<HostHandlers>> {
48 FerroProperty::register_attached::<InteractionHostHandlers, FerroObject, Option<HostHandlers>>(
49 "InteractionHostHandlers",
50 None,
51 )
52 }
53 }
54}
55
56#[derive(Clone)]
58struct HostHandlers(Rc<RefCell<Vec<Rc<dyn IDisposable>>>>);
59
60impl PartialEq for HostHandlers {
61 fn eq(&self, other: &Self) -> bool {
62 Rc::ptr_eq(&self.0, &other.0)
63 }
64}
65
66#[derive(Clone, Copy, PartialEq, Eq)]
69enum HandlerSet {
70 FromGetter,
71 FromChangedEvent,
72}
73
74impl Interaction {
75 pub fn get_behaviors(obj: &FerroObject) -> BehaviorCollection {
81 let behavior_collection = obj.get_value(Self::behaviors_property());
82 match behavior_collection {
83 Some(behavior_collection) => behavior_collection,
84 None => {
85 let behavior_collection = BehaviorCollection::new();
86 obj.set_value(Self::behaviors_property(), Some(behavior_collection.clone()));
87 Self::set_visual_tree_event_handlers_from_getter(obj);
88 behavior_collection
89 }
90 }
91 }
92
93 fn get_existing_behaviors(obj: &FerroObject) -> Option<BehaviorCollection> {
94 obj.get_value(Self::behaviors_property())
95 }
96
97 pub fn set_behaviors(obj: &FerroObject, value: Option<BehaviorCollection>) {
99 obj.set_value(Self::behaviors_property(), value);
100 }
101
102 pub fn execute_actions(
108 sender: Option<&BoxedValue>,
109 actions: Option<&ActionCollection>,
110 parameter: Option<&BoxedValue>,
111 ) -> Vec<BoxedValue> {
112 let Some(actions) = actions else {
113 return Vec::new();
114 };
115
116 let mut results = Vec::new();
117
118 for ferro_object in actions.snapshot().iter() {
119 let Some(action) = <dyn IAction>::of(ferro_object) else {
120 continue;
121 };
122
123 if let Some(result) = action.execute(sender, parameter) {
124 results.push(result);
125 }
126 }
127
128 results
129 }
130
131 fn behaviors_changed(e: &FerroPropertyChangedEventArgs<'_>) {
132 let old_collection = e.get_old_value::<Option<BehaviorCollection>>().flatten();
133 let new_collection = e.get_new_value::<Option<BehaviorCollection>>();
134
135 if old_collection == new_collection {
136 return;
137 }
138
139 let sender = e.sender().to_ref();
140 let is_attached_to_visual_tree =
141 sender.downcast_ref::<Visual>().is_some_and(|visual| visual.is_attached_to_visual_tree());
142
143 if let Some(old_collection) = &old_collection {
144 if old_collection.associated_object().is_some() {
145 if is_attached_to_visual_tree {
146 old_collection.detached_from_visual_tree();
147 }
148
149 old_collection.detach();
150 }
151 }
152
153 if let Some(new_collection) = &new_collection {
154 Self::set_visual_tree_event_handlers_from_changed_event(&sender);
155 new_collection.attach(&sender);
156
157 if is_attached_to_visual_tree {
158 new_collection.attached_to_visual_tree();
159 }
160 }
161 }
162
163 fn set_visual_tree_event_handlers_from_getter(obj: &FerroObject) {
164 Self::set_visual_tree_event_handlers(obj, HandlerSet::FromGetter);
165 }
166
167 fn set_visual_tree_event_handlers_from_changed_event(obj: &FerroObject) {
168 Self::set_visual_tree_event_handlers(obj, HandlerSet::FromChangedEvent);
169 }
170
171 fn set_visual_tree_event_handlers(obj: &FerroObject, set: HandlerSet) {
175 let property = InteractionHostHandlers::host_handlers_property();
176 let handlers = match obj.get_value(property) {
177 Some(handlers) => handlers,
178 None => {
179 let handlers = HostHandlers(Rc::new(RefCell::new(Vec::new())));
180 obj.set_value(property, Some(handlers.clone()));
181 handlers
182 }
183 };
184 let previous = std::mem::take(&mut *handlers.0.borrow_mut());
185 for subscription in previous {
186 subscription.dispose();
187 }
188
189 let host = obj.to_weak();
190 let mut subscriptions: Vec<Rc<dyn IDisposable>> = Vec::new();
191
192 if let Some(visual) = obj.downcast_ref::<Visual>() {
193 let weak = host.clone();
196 subscriptions.push(visual.attached_to_visual_tree(move |_| {
197 if let Some(d) = weak.upgrade() {
198 match set {
199 HandlerSet::FromGetter => Self::visual_attached_to_visual_tree_from_getter(&d),
200 HandlerSet::FromChangedEvent => Self::visual_attached_to_visual_tree_from_changed_event(&d),
201 }
202 }
203 }));
204 let weak = host.clone();
205 subscriptions.push(visual.detached_from_visual_tree(move |_| {
206 if let Some(d) = weak.upgrade() {
207 match set {
208 HandlerSet::FromGetter => Self::visual_detached_from_visual_tree_from_getter(&d),
209 HandlerSet::FromChangedEvent => Self::visual_detached_from_visual_tree_from_changed_event(&d),
210 }
211 }
212 }));
213 }
214
215 if let Some(styled_element) = obj.downcast_ref::<StyledElement>() {
216 let weak = host.clone();
219 subscriptions.push(styled_element.attached_to_logical_tree(move |_| {
220 Self::with_existing_behaviors(&weak, BehaviorCollection::attached_to_logical_tree);
221 }));
222 let weak = host.clone();
223 subscriptions.push(styled_element.detached_from_logical_tree(move |_| {
224 Self::with_existing_behaviors(&weak, BehaviorCollection::detached_from_logical_tree);
225 }));
226
227 let weak = host.clone();
230 subscriptions.push(styled_element.initialized(move || {
231 Self::with_existing_behaviors(&weak, BehaviorCollection::initialized);
232 }));
233
234 let weak = host.clone();
237 subscriptions.push(styled_element.data_context_changed(move || {
238 Self::with_existing_behaviors(&weak, BehaviorCollection::data_context_changed);
239 }));
240
241 let weak = host.clone();
244 subscriptions.push(styled_element.resources_changed(move |_| {
245 Self::with_existing_behaviors(&weak, BehaviorCollection::resources_changed);
246 }));
247
248 let weak = host.clone();
251 subscriptions.push(styled_element.actual_theme_variant_changed(move || {
252 Self::with_existing_behaviors(&weak, BehaviorCollection::actual_theme_variant_changed);
253 }));
254 }
255
256 if let Some(control) = obj.downcast_ref::<Control>() {
257 let weak = host.clone();
260 let token = control.loaded(move |_, _| {
261 Self::with_existing_behaviors(&weak, BehaviorCollection::loaded);
262 });
263 let control_weak = control.to_ref().downgrade();
264 subscriptions.push(Disposable::create(move || {
265 if let Some(control) = control_weak.upgrade() {
266 control.remove_handler(Control::loaded_event(), token);
267 }
268 }));
269
270 let weak = host.clone();
271 let token = control.unloaded(move |_, _| {
272 Self::with_existing_behaviors(&weak, BehaviorCollection::unloaded);
273 });
274 let control_weak = control.to_ref().downgrade();
275 subscriptions.push(Disposable::create(move || {
276 if let Some(control) = control_weak.upgrade() {
277 control.remove_handler(Control::unloaded_event(), token);
278 }
279 }));
280 }
281
282 if let Some(top_level) = obj.downcast_ref::<TopLevel>() {
283 let weak = host.clone();
284 subscriptions.push(top_level.opened(move || {
285 if let Some(d) = weak.upgrade() {
286 match set {
287 HandlerSet::FromGetter => Self::top_level_opened_from_getter(&d),
288 HandlerSet::FromChangedEvent => Self::top_level_opened_from_changed_event(&d),
289 }
290 }
291 }));
292 }
293
294 *handlers.0.borrow_mut() = subscriptions;
295 }
296
297 fn with_existing_behaviors(host: &WeakRef<FerroObject>, f: fn(&BehaviorCollection)) {
301 let Some(d) = host.upgrade() else {
302 return;
303 };
304
305 if let Some(behaviors) = Self::get_existing_behaviors(&d) {
306 f(&behaviors);
307 }
308 }
309
310 fn visual_attached_to_visual_tree_from_getter(d: &Ref<FerroObject>) {
313 let behaviors = Self::get_existing_behaviors(d);
314 if let Some(behaviors) = &behaviors {
315 behaviors.attach(d);
316 behaviors.attached_to_visual_tree();
317 }
318 }
319
320 fn visual_detached_from_visual_tree_from_getter(d: &Ref<FerroObject>) {
321 let Some(behaviors) = Self::get_existing_behaviors(d) else {
322 return;
323 };
324
325 behaviors.detached_from_visual_tree();
326
327 if let Some(top_level) = d.cast::<TopLevel>() {
328 Self::schedule_top_level_behavior_detach(top_level, behaviors);
329 } else {
330 behaviors.detach();
331 }
332 }
333
334 fn visual_attached_to_visual_tree_from_changed_event(d: &Ref<FerroObject>) {
335 if let Some(behaviors) = Self::get_existing_behaviors(d) {
336 behaviors.attached_to_visual_tree();
337 }
338 }
339
340 fn visual_detached_from_visual_tree_from_changed_event(d: &Ref<FerroObject>) {
341 let Some(behaviors) = Self::get_existing_behaviors(d) else {
342 return;
343 };
344
345 behaviors.detached_from_visual_tree();
346
347 if let Some(top_level) = d.cast::<TopLevel>() {
348 Self::schedule_top_level_behavior_detach(top_level, behaviors);
349 }
350 }
351
352 fn schedule_top_level_behavior_detach(top_level: Ref<TopLevel>, behaviors: BehaviorCollection) {
353 Dispatcher::ui_thread().post_local(
354 move || {
355 if !top_level.is_attached_to_visual_tree()
356 && top_level.get_value(Self::behaviors_property()).as_ref() == Some(&behaviors)
357 && behaviors.associated_object().is_some()
358 {
359 behaviors.detach();
360 }
361 },
362 DispatcherPriority::DEFAULT,
363 );
364 }
365
366 fn top_level_opened_from_getter(d: &Ref<FerroObject>) {
369 let behaviors = Self::get_existing_behaviors(d);
370 if let Some(behaviors) = &behaviors {
371 behaviors.attach(d);
372 behaviors.opened();
373 }
374 }
375
376 fn top_level_opened_from_changed_event(d: &Ref<FerroObject>) {
377 if let Some(behaviors) = Self::get_existing_behaviors(d) {
378 behaviors.opened();
379 }
380 }
381}