ferroui_xaml_interactions/core/
async_action_group.rs1use super::{attach_actions_to_logical_tree, detach_actions_from_logical_tree};
4use crate::action_task::ActionTask;
5use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
6use ferroui_base::{
7 ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObject, FerroObjectImpl,
8 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledElementImplExt,
9 StyledProperty,
10};
11use ferroui_xaml_interactivity::{ActionCollection, IAction, StyledElementAction, StyledElementActionImpl};
12use std::rc::Rc;
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16pub enum AsyncActionMode {
17 #[default]
20 Sequence,
21
22 Parallel,
24}
25
26impl std::fmt::Display for AsyncActionMode {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.write_str(match self {
29 Self::Sequence => "Sequence",
30 Self::Parallel => "Parallel",
31 })
32 }
33}
34
35#[repr(C)]
39pub struct AsyncActionGroup {
40 base: StyledElementAction,
41}
42
43ferro_class!(AsyncActionGroup: StyledElementAction);
44ferro_class_info!(AsyncActionGroup {
45 new: AsyncActionGroup::new,
46 markup: {
47 content: Actions,
48 },
49});
50
51ferro_properties! {
52 impl AsyncActionGroup {
53 pub fn mode_property() -> StyledProperty<AsyncActionMode> {
55 FerroProperty::register::<AsyncActionGroup, _>("Mode", AsyncActionMode::Sequence)
56 }
57
58 pub fn actions_property() -> StyledProperty<Option<ActionCollection>> {
60 FerroProperty::register::<AsyncActionGroup, _>("Actions", None)
61 }
62 }
63}
64
65ferroui_base::ferro_overrides! {
66 impl FerroObjectImpl for AsyncActionGroup {
67 fn constructed(this: &Self) {
69 Self::parent_constructed(this);
70 this.set_current_value(Self::actions_property(), Some(ActionCollection::new()));
71 }
72
73 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
74 Self::parent_on_property_changed(this, change);
75
76 if change.property() == Self::actions_property().as_property() {
77 let old_actions = change.get_old_value::<Option<ActionCollection>>().flatten();
78 let new_actions = change.get_new_value::<Option<ActionCollection>>();
79
80 if let Some(old_actions) = &old_actions {
81 if this.is_attached_to_logical_tree() {
82 detach_actions_from_logical_tree(Some(old_actions), this);
83 }
84 }
85
86 if let Some(new_actions) = &new_actions {
87 if this.is_attached_to_logical_tree() {
88 attach_actions_to_logical_tree(Some(new_actions), this);
89 }
90 }
91 }
92 }
93 }
94}
95
96ferroui_base::ferro_overrides! {
97 impl StyledElementImpl for AsyncActionGroup {
98 fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
99 Self::parent_on_attached_to_logical_tree(this, e);
100 attach_actions_to_logical_tree(this.actions().as_ref(), this);
101 }
102
103 fn on_detached_from_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
104 detach_actions_from_logical_tree(this.actions().as_ref(), this);
105 Self::parent_on_detached_from_logical_tree(this, e);
106 }
107 }
108}
109
110impl StyledElementActionImpl for AsyncActionGroup {
111 fn execute(this: &Self, sender: Option<&BoxedValue>, parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
113 this.actions()?;
114
115 Some(Rc::new(this.execute_async(sender, parameter)))
118 }
119}
120
121fn task_of(result: Option<&BoxedValue>) -> Option<ActionTask> {
123 result.and_then(|result| result.downcast_ref::<ActionTask>()).cloned()
124}
125
126async fn execute_sequence(
129 items: Rc<Vec<Ref<FerroObject>>>,
130 start: usize,
131 sender: Option<BoxedValue>,
132 parameter: Option<BoxedValue>,
133) {
134 for item in items.iter().skip(start) {
135 let result = match <dyn IAction>::of(item) {
136 Some(action) => action.execute(sender.as_ref(), parameter.as_ref()),
137 None => continue,
138 };
139
140 if let Some(task) = task_of(result.as_ref()) {
141 let _ = task.await;
142 }
143 }
144}
145
146impl AsyncActionGroup {
147 pub fn construct() -> Self {
149 Self { base: StyledElementAction::construct() }
150 }
151
152 pub fn new() -> Ref<Self> {
154 instantiate(Self::construct())
155 }
156
157 pub fn mode(&self) -> AsyncActionMode {
159 self.get_value(Self::mode_property())
160 }
161
162 pub fn set_mode(&self, value: AsyncActionMode) {
163 self.set_value(Self::mode_property(), value)
164 }
165
166 pub fn actions(&self) -> Option<ActionCollection> {
168 self.get_value(Self::actions_property())
169 }
170
171 pub fn set_actions(&self, value: Option<ActionCollection>) {
172 self.set_value(Self::actions_property(), value)
173 }
174
175 fn execute_async(&self, sender: Option<&BoxedValue>, parameter: Option<&BoxedValue>) -> ActionTask {
179 let Some(actions) = self.actions() else {
180 return ActionTask::completed();
181 };
182
183 let items = actions.snapshot();
184
185 if self.mode() == AsyncActionMode::Sequence {
186 for (index, item) in items.iter().enumerate() {
187 let Some(action) = <dyn IAction>::of(item) else {
188 continue;
189 };
190
191 let result = action.execute(sender, parameter);
192 if let Some(task) = task_of(result.as_ref()) {
193 let rest = items.clone();
194 let sender = sender.cloned();
195 let parameter = parameter.cloned();
196 return ActionTask::spawn(async move {
197 let _ = task.await;
198 execute_sequence(rest, index + 1, sender, parameter).await;
199 });
200 }
201 }
202
203 return ActionTask::completed();
204 }
205
206 let mut tasks = Vec::new();
208 for item in items.iter() {
209 let Some(action) = <dyn IAction>::of(item) else {
210 continue;
211 };
212
213 let result = action.execute(sender, parameter);
214 if let Some(task) = task_of(result.as_ref()) {
215 tasks.push(task);
216 }
217 }
218
219 if tasks.is_empty() {
220 return ActionTask::completed();
221 }
222
223 ActionTask::spawn(async move {
224 for task in tasks {
225 let _ = task.await;
226 }
227 })
228 }
229}