Skip to main content

ferroui_base/interactivity/
routed_event_args.rs

1use super::{RoutedEvent, RoutingStrategies};
2use crate::{FerroObject, Nullable, Ref};
3use std::any::{Any, TypeId};
4use std::cell::{Cell, RefCell};
5use std::ops::Deref;
6use std::rc::Rc;
7
8/// Implemented by every routed event args type.
9///
10/// Routed event args form a small class hierarchy rooted at
11/// [`RoutedEventArgs`]: a derived args type embeds its base as its first
12/// field (named `base`) and derefs to it. This trait gives the runtime view
13/// of that hierarchy, so that an event raised with derived args can be
14/// delivered to a handler written against any of its base args types.
15///
16/// Implement it with [`ferro_routed_event_args!`](crate::ferro_routed_event_args).
17pub trait IRoutedEventArgs: Any {
18    /// The base routed event args: the routing state of the event.
19    fn as_routed_event_args(&self) -> &RoutedEventArgs;
20
21    /// Returns the args viewed as the args type identified by `type_id`, if
22    /// the args are of that type or of a type derived from it.
23    fn query_args(&self, type_id: TypeId) -> Option<&dyn Any>;
24
25    /// Whether this args type is the args type identified by `type_id` or is
26    /// derived from it.
27    fn is_args_type(type_id: TypeId) -> bool
28    where
29        Self: Sized;
30
31    /// A shared handle to these args: what untyped code (a handler wired up
32    /// by markup) is given, since a borrow cannot be held in an untyped
33    /// value. The handle refers to the live args: the state handlers change
34    /// (`handled`, the source, a cancel flag, ..) is shared with them.
35    fn share(&self) -> Rc<dyn IRoutedEventArgs>;
36}
37
38/// Handles compare by identity (reference equality): two handles are equal
39/// when they refer to the same args, so that they can be held in untyped
40/// values.
41impl PartialEq for dyn IRoutedEventArgs {
42    #[inline]
43    fn eq(&self, other: &Self) -> bool {
44        self.as_routed_event_args() == other.as_routed_event_args()
45    }
46}
47
48impl dyn IRoutedEventArgs {
49    /// Returns the args as `T` if they are of type `T` or of a type derived
50    /// from it (C# `e as T`).
51    #[inline]
52    pub fn downcast_ref<T: IRoutedEventArgs>(&self) -> Option<&T> {
53        self.query_args(TypeId::of::<T>()).and_then(<dyn Any>::downcast_ref::<T>)
54    }
55
56    /// Whether the args are of type `T` or of a type derived from it.
57    #[inline]
58    pub fn is<T: IRoutedEventArgs>(&self) -> bool {
59        self.query_args(TypeId::of::<T>()).is_some()
60    }
61}
62
63impl Deref for dyn IRoutedEventArgs {
64    type Target = RoutedEventArgs;
65
66    #[inline]
67    fn deref(&self) -> &RoutedEventArgs {
68        self.as_routed_event_args()
69    }
70}
71
72/// Provides state information and data specific to a routed event.
73///
74/// The routing state (`handled`, `route`, `source`) is mutated while the
75/// event travels along its route, so it is interiorly mutable: handlers
76/// receive the args by shared reference.
77///
78/// The args are a reference object: a copy shares the routing state with
79/// the args it was made from (marking a copy as handled marks the event as
80/// handled), and copies compare by identity. The same holds for the args
81/// types derived from this one, whose own mutable state is shared too.
82#[derive(Clone, Default)]
83pub struct RoutedEventArgs {
84    state: Rc<RoutedEventArgsState>,
85}
86
87#[derive(Default)]
88struct RoutedEventArgsState {
89    handled: Cell<bool>,
90    routed_event: Cell<Option<RoutedEvent>>,
91    route: Cell<RoutingStrategies>,
92    source: RefCell<Option<Ref<FerroObject>>>,
93}
94
95impl PartialEq for RoutedEventArgs {
96    #[inline]
97    fn eq(&self, other: &Self) -> bool {
98        Rc::ptr_eq(&self.state, &other.state)
99    }
100}
101
102impl RoutedEventArgs {
103    /// Creates args with no routed event.
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    /// Creates args for a routed event.
109    pub fn with_event<T: ?Sized>(routed_event: &RoutedEvent<T>) -> Self {
110        let result = Self::default();
111        result.state.routed_event.set(Some(routed_event.as_routed_event()));
112        result
113    }
114
115    /// Creates args for a routed event with a source.
116    pub fn with_event_and_source<T: ?Sized>(
117        routed_event: &RoutedEvent<T>,
118        source: impl Into<Nullable<FerroObject>>,
119    ) -> Self {
120        let result = Self::with_event(routed_event);
121        result.state.source.replace(source.into().0);
122        result
123    }
124
125    /// Whether the routed event has already been handled.
126    ///
127    /// Once handled, the event keeps travelling along its route but is only
128    /// delivered to handlers registered for handled events too.
129    #[inline]
130    pub fn handled(&self) -> bool {
131        self.state.handled.get()
132    }
133
134    /// Marks the routed event as handled or unhandled.
135    #[inline]
136    pub fn set_handled(&self, value: bool) {
137        self.state.handled.set(value)
138    }
139
140    /// The routed event associated with these args.
141    #[inline]
142    pub fn routed_event(&self) -> Option<RoutedEvent> {
143        self.state.routed_event.get()
144    }
145
146    /// Sets the routed event associated with these args.
147    #[inline]
148    pub fn set_routed_event<T: ?Sized>(&self, value: Option<&RoutedEvent<T>>) {
149        self.state.routed_event.set(value.map(RoutedEvent::as_routed_event))
150    }
151
152    /// The routing strategy currently being applied to the event.
153    #[inline]
154    pub fn route(&self) -> RoutingStrategies {
155        self.state.route.get()
156    }
157
158    /// Sets the routing strategy currently being applied to the event.
159    #[inline]
160    pub fn set_route(&self, value: RoutingStrategies) {
161        self.state.route.set(value)
162    }
163
164    /// The object that raised the event.
165    #[inline]
166    pub fn source(&self) -> Option<Ref<FerroObject>> {
167        self.state.source.borrow().clone()
168    }
169
170    /// Sets the object that raised the event.
171    pub fn set_source(&self, value: impl Into<Nullable<FerroObject>>) {
172        // The previous source is dropped after the borrow is released.
173        let old = self.state.source.replace(value.into().0);
174        drop(old);
175    }
176
177    /// Whether `object` is the source of the event (C# `e.Source == object`).
178    #[inline]
179    pub fn is_source(&self, object: &FerroObject) -> bool {
180        match &*self.state.source.borrow() {
181            Some(source) => std::ptr::eq::<FerroObject>(&**source, object),
182            None => false,
183        }
184    }
185}
186
187impl IRoutedEventArgs for RoutedEventArgs {
188    #[inline]
189    fn as_routed_event_args(&self) -> &RoutedEventArgs {
190        self
191    }
192
193    #[inline]
194    fn query_args(&self, type_id: TypeId) -> Option<&dyn Any> {
195        if type_id == TypeId::of::<RoutedEventArgs>() {
196            Some(self)
197        } else {
198            None
199        }
200    }
201
202    #[inline]
203    fn is_args_type(type_id: TypeId) -> bool {
204        type_id == TypeId::of::<RoutedEventArgs>()
205    }
206
207    fn share(&self) -> Rc<dyn IRoutedEventArgs> {
208        Rc::new(self.clone())
209    }
210}
211
212/// Declares a routed event args type derived from another one.
213///
214/// ```ignore
215/// pub struct TextInputEventArgs {
216///     base: RoutedEventArgs,      // always first, always named `base`
217///     pub text: Option<String>,
218/// }
219///
220/// ferro_routed_event_args!(TextInputEventArgs: RoutedEventArgs);
221/// ```
222///
223/// Generates `Deref<Target = Base>` and the
224/// [`IRoutedEventArgs`](crate::interactivity::IRoutedEventArgs)
225/// implementation that makes the args deliverable to handlers written
226/// against `TextInputEventArgs` or any of its base args types.
227#[macro_export]
228macro_rules! ferro_routed_event_args {
229    ($name:ident : $base:ty) => {
230        impl ::std::ops::Deref for $name {
231            type Target = $base;
232
233            #[inline]
234            fn deref(&self) -> &$base {
235                &self.base
236            }
237        }
238
239        impl $crate::interactivity::IRoutedEventArgs for $name {
240            #[inline]
241            fn as_routed_event_args(&self) -> &$crate::interactivity::RoutedEventArgs {
242                $crate::interactivity::IRoutedEventArgs::as_routed_event_args(&self.base)
243            }
244
245            #[inline]
246            fn query_args(&self, type_id: ::std::any::TypeId) -> ::std::option::Option<&dyn ::std::any::Any> {
247                if type_id == ::std::any::TypeId::of::<$name>() {
248                    ::std::option::Option::Some(self)
249                } else {
250                    $crate::interactivity::IRoutedEventArgs::query_args(&self.base, type_id)
251                }
252            }
253
254            #[inline]
255            fn is_args_type(type_id: ::std::any::TypeId) -> bool {
256                type_id == ::std::any::TypeId::of::<$name>()
257                    || <$base as $crate::interactivity::IRoutedEventArgs>::is_args_type(type_id)
258            }
259
260            fn share(&self) -> ::std::rc::Rc<dyn $crate::interactivity::IRoutedEventArgs> {
261                ::std::rc::Rc::new(::std::clone::Clone::clone(self))
262            }
263        }
264    };
265}