Skip to main content

ferroui_xaml_interactions_custom/actions/
call_method_async_action.rs

1//! Port of `Actions/CallMethodAsyncAction.cs`.
2
3use ferroui_base::{
4    ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroProperty, Ref,
5    StyledElementImpl, StyledProperty,
6};
7use ferroui_xaml_interactivity::helpers::member_lookup::{find_methods, type_name_of};
8use ferroui_xaml_interactivity::{StyledElementAction, StyledElementActionImpl};
9use std::rc::Rc;
10
11/// Calls a method of the [`target_object`](Self::target_object) (or of the sender) by name: a
12/// method without parameters, or with two parameters (the sender and the parameter of the action)
13/// whose first parameter is untyped.
14///
15/// The method is found among the methods the markup metadata of the target declares (the
16/// reflection of the reference).
17#[repr(C)]
18pub struct CallMethodAsyncAction {
19    base: StyledElementAction,
20}
21
22ferro_class!(CallMethodAsyncAction: StyledElementAction);
23ferro_class_info!(CallMethodAsyncAction {
24    new: CallMethodAsyncAction::new,
25    markup: {
26        property_attributes: [
27            TargetObject: [ResolveByName],
28        ],
29    },
30});
31ferroui_base::ferro_impl_classes!(CallMethodAsyncAction: FerroObjectImpl, StyledElementImpl);
32
33ferro_properties! {
34    impl CallMethodAsyncAction {
35        /// Identifies the `MethodName` property.
36        pub fn method_name_property() -> StyledProperty<Option<String>> {
37            FerroProperty::register::<CallMethodAsyncAction, _>("MethodName", None)
38        }
39
40        /// Identifies the `TargetObject` property.
41        pub fn target_object_property() -> StyledProperty<Option<BoxedValue>> {
42            FerroProperty::register::<CallMethodAsyncAction, _>("TargetObject", None)
43        }
44    }
45}
46
47impl StyledElementActionImpl for CallMethodAsyncAction {
48    /// Executes the action.
49    ///
50    /// # Panics
51    /// Panics when the target has no method of the name with an expected signature, or when the
52    /// call fails (the exceptions of the reference).
53    fn execute(this: &Self, sender: Option<&BoxedValue>, parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
54        if !this.is_enabled() {
55            return Some(Rc::new(false));
56        }
57
58        let target = if this.get_value(Self::target_object_property()).is_some() {
59            this.target_object()
60        } else {
61            sender.cloned()
62        };
63        let method_name = this.method_name().filter(|name| !name.is_empty());
64        let (Some(target), Some(method_name)) = (target, method_name) else {
65            return Some(Rc::new(false));
66        };
67
68        let method_info = find_methods(&*target, &method_name).into_iter().find(|method| {
69            let parameters = method.parameters;
70            parameters.is_empty() || (parameters.len() == 2 && parameters[0]().is_object())
71        });
72        let Some(method_info) = method_info else {
73            panic!(
74                "Cannot find method named {} on object of type {} that matches the expected signature.",
75                method_name,
76                type_name_of(&target)
77            );
78        };
79
80        let mut arguments = Vec::new();
81        if !method_info.is_static {
82            arguments.push(Some(target.clone()));
83        }
84        if method_info.parameters.len() == 2 {
85            arguments.push(sender.cloned());
86            arguments.push(parameter.cloned());
87        }
88
89        // The exception of the invoked method propagates (`TargetInvocationException`).
90        // Deviation (DEVIATIONS.md, Behaviors): a task the method returns is not awaited in a job
91        // of the dispatcher (the reference awaits it only to observe it); it runs on its own.
92        if let Err(error) = (method_info.invoke)(&arguments) {
93            panic!("{error}");
94        }
95
96        Some(Rc::new(true))
97    }
98}
99
100impl CallMethodAsyncAction {
101    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
102    pub fn construct() -> Self {
103        Self { base: StyledElementAction::construct() }
104    }
105
106    /// Creates an action.
107    pub fn new() -> Ref<Self> {
108        instantiate(Self::construct())
109    }
110
111    /// Gets or sets the name of the method to call. This is a property of the object model.
112    pub fn method_name(&self) -> Option<String> {
113        self.get_value(Self::method_name_property())
114    }
115
116    pub fn set_method_name(&self, value: Option<String>) {
117        self.set_value(Self::method_name_property(), value)
118    }
119
120    /// Gets or sets the object that exposes the method. If not set, the sender is used. Markup
121    /// resolves a name given for it to the named element.
122    pub fn target_object(&self) -> Option<BoxedValue> {
123        self.get_value(Self::target_object_property())
124    }
125
126    pub fn set_target_object(&self, value: Option<BoxedValue>) {
127        self.set_value(Self::target_object_property(), value)
128    }
129}