Skip to main content

ferroui_xaml_interactions/core/
task_completed_trigger.rs

1//! Port of `Core/TaskCompletedTrigger.cs`.
2
3use crate::action_task::{ActionTask, CatchUnwind};
4use ferroui_base::threading::{Dispatcher, DispatcherPriority};
5use ferroui_base::{
6    ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
7    FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty,
8};
9use ferroui_xaml_interactivity::{Interaction, StyledElementBehaviorImpl, StyledElementTrigger};
10use std::rc::Rc;
11
12/// A trigger that invokes its actions when the supplied task completes.
13///
14/// The task is an [`ActionTask`] (the `Task` of the reference); the actions run in a job of the
15/// dispatcher after it completed, whether it ran to the end, failed or was canceled.
16#[repr(C)]
17pub struct TaskCompletedTrigger {
18    base: StyledElementTrigger,
19}
20
21ferro_class!(TaskCompletedTrigger: StyledElementTrigger);
22ferro_class_info!(TaskCompletedTrigger { new: TaskCompletedTrigger::new });
23ferroui_base::ferro_impl_classes!(TaskCompletedTrigger: StyledElementImpl, StyledElementBehaviorImpl);
24
25ferro_properties! {
26    impl TaskCompletedTrigger {
27        /// Identifies the `Task` property.
28        pub fn task_property() -> StyledProperty<Option<ActionTask>> {
29            FerroProperty::register::<TaskCompletedTrigger, _>("Task", None)
30        }
31    }
32}
33
34ferroui_base::ferro_overrides! {
35    impl FerroObjectImpl for TaskCompletedTrigger {
36        fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
37            Self::parent_on_property_changed(this, change);
38
39            if change.property() == Self::task_property().as_property() {
40                this.on_task_changed(change.get_new_value::<Option<ActionTask>>());
41            }
42        }
43    }
44}
45
46impl TaskCompletedTrigger {
47    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
48    pub fn construct() -> Self {
49        Self { base: StyledElementTrigger::construct() }
50    }
51
52    /// Creates a trigger.
53    pub fn new() -> Ref<Self> {
54        instantiate(Self::construct())
55    }
56
57    /// Gets or sets the task monitored by the trigger.
58    pub fn task(&self) -> Option<ActionTask> {
59        self.get_value(Self::task_property())
60    }
61
62    pub fn set_task(&self, value: Option<ActionTask>) {
63        self.set_value(Self::task_property(), value)
64    }
65
66    fn on_task_changed(&self, task: Option<ActionTask>) {
67        let Some(task) = task else {
68            return;
69        };
70
71        // `task.ContinueWith(..)`: the continuation runs whatever the outcome of the task.
72        let trigger: Ref<TaskCompletedTrigger> = self.to_ref();
73        drop(ActionTask::spawn(async move {
74            let _ = CatchUnwind::new(task).await;
75            Dispatcher::ui_thread().post_local(
76                move || {
77                    let sender = trigger
78                        .associated_object()
79                        .map(|associated_object| Rc::new(associated_object) as BoxedValue);
80                    Interaction::execute_actions(sender.as_ref(), trigger.actions().as_ref(), None);
81                },
82                DispatcherPriority::DEFAULT,
83            );
84        }));
85    }
86}