ferroui_xaml_interactivity/triggers/
event_trigger_base.rs1use crate::helpers::member_lookup::{find_plain_event, find_routed_event, object_of, subscribe_plain_event};
4use crate::{
5 AddEventHandlerRegistry, DisposableAction, Interaction, StyledElementBehaviorImpl, StyledElementBehaviorImplExt,
6 StyledElementTrigger,
7};
8use ferroui_base::data::core::{ValueTypes, WeakValue};
9use ferroui_base::interactivity::{IRoutedEventArgs, Interactive, RouteHandler};
10use ferroui_base::metadata::{MarkupDelegate, MarkupValue};
11use ferroui_base::reactive::IDisposable;
12use ferroui_base::utilities::EventArgs;
13use ferroui_base::{
14 ferro_class, ferro_class_info, ferro_properties, BoxedValue, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
15 FerroPropertyChangedEventArgs, StyledElementImpl, StyledProperty, WeakRef,
16};
17use ferroui_controls::Control;
18use std::cell::{Cell, RefCell};
19use std::rc::Rc;
20
21const EVENT_NAME_DEFAULT_VALUE: &str = "AttachedToVisualTree";
22
23#[repr(C)]
31pub struct EventTriggerBase {
32 base: StyledElementTrigger,
33 resolved_source: RefCell<Option<WeakValue>>,
36 event_handler: RefCell<Option<Rc<dyn IDisposable>>>,
37 is_loaded_event_registered: Cell<bool>,
38 loaded_event_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
39 try_register_event_handler_disposable: RefCell<Option<Rc<dyn IDisposable>>>,
40}
41
42ferro_class! {
43 EventTriggerBase: StyledElementTrigger, virtuals EventTriggerBaseImpl: StyledElementBehaviorImpl {
44 fn attached_to_visual_tree(this, sender: Option<&BoxedValue>, event_args: &BoxedValue);
47 fn on_event(this, event_args: Option<&BoxedValue>);
49 }
50}
51ferro_class_info!(EventTriggerBase {
52 markup: {
53 property_attributes: [
54 SourceObject: [ResolveByName],
55 ],
56 },
57});
58
59ferroui_base::ferro_impl_classes!(EventTriggerBase: StyledElementImpl);
60
61impl EventTriggerBaseImpl for EventTriggerBase {
62 fn attached_to_visual_tree(this: &Self, _sender: Option<&BoxedValue>, event_args: &BoxedValue) {
63 this.on_event(Some(event_args));
64 }
65
66 fn on_event(this: &Self, event_args: Option<&BoxedValue>) {
67 if !this.is_enabled() {
68 return;
69 }
70
71 let resolved_source = this.resolved_source();
72 Interaction::execute_actions(resolved_source.as_ref(), this.actions().as_ref(), event_args);
73 }
74}
75
76ferro_properties! {
77 impl EventTriggerBase {
78 pub fn event_name_property() -> StyledProperty<Option<String>> {
80 FerroProperty::register::<EventTriggerBase, _>("EventName", Some(EVENT_NAME_DEFAULT_VALUE.to_string()))
81 }
82
83 pub fn source_object_property() -> StyledProperty<Option<BoxedValue>> {
85 FerroProperty::register::<EventTriggerBase, _>("SourceObject", None)
86 }
87 }
88}
89
90ferroui_base::ferro_overrides! {
91 impl FerroObjectImpl for EventTriggerBase {
92 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
93 Self::parent_on_property_changed(this, change);
94
95 if change.property() == Self::event_name_property().as_property() {
96 let resolved_source = this.resolved_source();
97 let old_event_name = change.get_old_value::<Option<String>>().flatten();
98 if let Some(old_event_name) = old_event_name {
99 this.unregister_event(Some(&old_event_name), resolved_source.as_ref());
100 }
101
102 let new_event_name = change.get_new_value::<Option<String>>();
103 if let Some(new_event_name) = new_event_name {
104 this.register_event(Some(&new_event_name), resolved_source.as_ref());
105 }
106 } else if change.property() == Self::source_object_property().as_property() {
107 this.set_resolved_source(this.event_name().as_deref(), this.source_object().or_else(|| this.associated_source()));
108 }
109 }
110 }
111}
112
113ferroui_base::ferro_overrides! {
114 impl StyledElementBehaviorImpl for EventTriggerBase {
115 fn on_attached(this: &Self) {
117 Self::parent_on_attached(this);
118
119 this.set_resolved_source(this.event_name().as_deref(), this.source_object().or_else(|| this.associated_source()));
120 }
121
122 fn on_detaching(this: &Self) {
124 Self::parent_on_detaching(this);
125 this.set_resolved_source(this.event_name().as_deref(), None);
126 }
127 }
128}
129
130fn same_source(a: Option<&BoxedValue>, b: Option<&BoxedValue>) -> bool {
132 match (a, b) {
133 (None, None) => true,
134 (Some(a), Some(b)) => match (object_of(a), object_of(b)) {
135 (Some(a), Some(b)) => a.ptr_eq(&b),
136 (None, None) => ValueTypes::identity_equals(Some(a), Some(b)),
137 _ => false,
138 },
139 _ => false,
140 }
141}
142
143impl EventTriggerBase {
144 pub fn construct() -> Self {
146 Self {
147 base: StyledElementTrigger::construct(),
148 resolved_source: RefCell::new(None),
149 event_handler: RefCell::new(None),
150 is_loaded_event_registered: Cell::new(false),
151 loaded_event_subscription: RefCell::new(None),
152 try_register_event_handler_disposable: RefCell::new(None),
153 }
154 }
155
156 pub fn event_name(&self) -> Option<String> {
158 self.get_value(Self::event_name_property())
159 }
160
161 pub fn set_event_name(&self, value: Option<String>) {
162 self.set_value(Self::event_name_property(), value)
163 }
164
165 pub fn source_object(&self) -> Option<BoxedValue> {
169 self.get_value(Self::source_object_property())
170 }
171
172 pub fn set_source_object(&self, value: Option<BoxedValue>) {
173 self.set_value(Self::source_object_property(), value)
174 }
175
176 pub fn resolved_source(&self) -> Option<BoxedValue> {
178 self.resolved_source.borrow().as_ref().and_then(WeakValue::upgrade)
179 }
180
181 fn associated_source(&self) -> Option<BoxedValue> {
183 self.associated_object().map(|associated_object| Rc::new(associated_object) as BoxedValue)
184 }
185
186 fn set_resolved_source(&self, event_name: Option<&str>, new_source: Option<BoxedValue>) {
187 let resolved_source = self.resolved_source();
188 if same_source(resolved_source.as_ref(), new_source.as_ref()) {
189 return;
190 }
191
192 if resolved_source.is_some() {
193 self.unregister_event(event_name, resolved_source.as_ref());
194 }
195
196 *self.resolved_source.borrow_mut() = new_source.as_ref().map(WeakValue::new);
197
198 if new_source.is_some() {
199 self.register_event(event_name, new_source.as_ref());
200 }
201 }
202
203 fn register_event(&self, event_name: Option<&str>, resolved_source: Option<&BoxedValue>) {
204 let Some(event_name) = event_name.filter(|name| !name.is_empty()) else {
205 return;
206 };
207
208 if event_name != EVENT_NAME_DEFAULT_VALUE {
209 self.register_user_event(event_name, resolved_source);
210 } else if !self.is_loaded_event_registered.get() {
211 self.register_default_event(resolved_source);
212 }
213 }
214
215 fn unregister_event(&self, event_name: Option<&str>, resolved_source: Option<&BoxedValue>) {
216 let Some(event_name) = event_name.filter(|name| !name.is_empty()) else {
217 return;
218 };
219
220 if event_name != EVENT_NAME_DEFAULT_VALUE {
221 self.unregister_user_event(event_name, resolved_source);
222 } else if self.is_loaded_event_registered.get() {
223 self.unregister_default_event(resolved_source);
224 }
225 }
226
227 fn handler(&self) -> Rc<dyn Fn(Option<&BoxedValue>, &BoxedValue)> {
230 let this: WeakRef<EventTriggerBase> = self.to_ref().downgrade();
231 Rc::new(move |sender: Option<&BoxedValue>, event_args: &BoxedValue| {
232 if let Some(this) = this.upgrade() {
233 this.attached_to_visual_tree(sender, event_args);
234 }
235 })
236 }
237
238 fn register_user_event(&self, event_name: &str, resolved_source: Option<&BoxedValue>) {
239 let Some(resolved_source) = resolved_source else {
240 return;
241 };
242
243 let disposable = AddEventHandlerRegistry::try_register_event_handler(resolved_source, event_name, self.handler());
244 if let Some(disposable) = disposable {
245 *self.try_register_event_handler_disposable.borrow_mut() = Some(disposable);
246 return;
247 }
248
249 self.add_event_handler(event_name, resolved_source);
250 }
251
252 fn unregister_user_event(&self, _event_name: &str, resolved_source: Option<&BoxedValue>) {
253 let disposable = self.try_register_event_handler_disposable.borrow_mut().take();
254 if let Some(disposable) = disposable {
255 disposable.dispose();
256 return;
257 }
258
259 if self.event_handler.borrow().is_none() {
260 return;
261 }
262
263 let event_handler = self.event_handler.borrow_mut().take();
264 if resolved_source.is_some() {
265 if let Some(event_handler) = event_handler {
266 event_handler.dispose();
267 }
268 }
269 }
270
271 fn add_event_handler(&self, event_name: &str, resolved_source: &BoxedValue) {
276 let handler = self.handler();
277
278 if let Some(interactive) = object_of(resolved_source).and_then(|object| object.cast::<Interactive>()) {
279 if let Some(routed_event) = find_routed_event(interactive.get_type(), event_name) {
280 let route_handler: RouteHandler = Rc::new(move |sender: &Interactive, event_args: &dyn IRoutedEventArgs| {
281 let sender: BoxedValue = Rc::new(sender.to_ref());
282 let event_args: BoxedValue = Rc::new(event_args.share());
283 handler(Some(&sender), &event_args);
284 });
285 let token =
286 interactive.add_route_handler(&routed_event, route_handler, Interactive::DEFAULT_ROUTES, false);
287 let interactive = interactive.downgrade();
288 *self.event_handler.borrow_mut() = Some(DisposableAction::create(move || {
289 if let Some(interactive) = interactive.upgrade() {
290 interactive.remove_handler(&routed_event, token);
291 }
292 }));
293 return;
294 }
295 }
296
297 let Some(event_info) = find_plain_event(&**resolved_source, event_name) else {
298 return;
299 };
300
301 let delegate = MarkupDelegate::new(move |arguments: &[MarkupValue]| -> MarkupValue {
302 let (sender, event_args) = match arguments {
303 [] => (None, None),
304 [event_args] => (None, event_args.clone()),
305 [sender, event_args, ..] => (sender.clone(), event_args.clone()),
306 };
307 let event_args = event_args.unwrap_or_else(|| Rc::new(EventArgs::EMPTY) as BoxedValue);
308 handler(sender.as_ref(), &event_args);
309 None
310 });
311 *self.event_handler.borrow_mut() = subscribe_plain_event(event_info, resolved_source, delegate);
312 }
313
314 fn register_default_event(&self, resolved_source: Option<&BoxedValue>) {
315 let Some(element) = resolved_source.and_then(object_of).and_then(|object| object.cast::<Control>()) else {
316 return;
317 };
318
319 if !Self::is_element_loaded(&element) {
320 self.is_loaded_event_registered.set(true);
321 let handler = self.handler();
322 let sender = element.downgrade();
323 let subscription = element.attached_to_visual_tree(move |e| {
324 let sender: Option<BoxedValue> = sender.upgrade().map(|sender| Rc::new(sender) as BoxedValue);
325 let event_args: BoxedValue = Rc::new(e.clone());
326 handler(sender.as_ref(), &event_args);
327 });
328 *self.loaded_event_subscription.borrow_mut() = Some(subscription);
329 }
330 }
331
332 fn unregister_default_event(&self, resolved_source: Option<&BoxedValue>) {
333 self.is_loaded_event_registered.set(false);
334 let subscription = self.loaded_event_subscription.borrow_mut().take();
335 let is_control = resolved_source.and_then(object_of).is_some_and(|object| object.is::<Control>());
336 if let (true, Some(subscription)) = (is_control, subscription) {
337 subscription.dispose();
338 }
339 }
340
341 fn is_element_loaded(element: &Control) -> bool {
342 element.parent().is_some()
343 }
344}