ferroui_xaml_interactivity/actions/
invoke_command_action_base.rs1use crate::helpers::{CommandCanExecuteIsEnabledBinder, CommandCanExecuteObserver};
4use crate::{IActionLogicalTreeLifecycle, IBehavior, StyledElementAction, StyledElementActionImpl};
5use ferroui_base::data::converters::IValueConverter;
6use ferroui_base::data::core::ValueType;
7use ferroui_base::input::{ICommand, InputElement};
8use ferroui_base::utilities::CultureInfo;
9use ferroui_base::{
10 ferro_class, ferro_class_info, ferro_properties, BoxedValue, DirectProperty, FerroObjectExtensions, FerroObjectImpl,
11 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty,
12};
13use std::cell::{Cell, OnceCell};
14use std::rc::Rc;
15
16#[repr(C)]
26pub struct InvokeCommandActionBase {
27 base: StyledElementAction,
28 command_can_execute_observer: OnceCell<CommandCanExecuteObserver>,
29 command_can_execute_is_enabled_binder: CommandCanExecuteIsEnabledBinder,
30 can_execute_command: Cell<bool>,
31 pass_event_args_to_command: Cell<bool>,
32}
33
34ferro_class!(InvokeCommandActionBase: StyledElementAction);
35ferro_class_info!(InvokeCommandActionBase {
36 markup: {
37 properties: [
38 PassEventArgsToCommand: bool {
39 get: |this: &Ref<InvokeCommandActionBase>| this.pass_event_args_to_command(),
40 set: |this: &Ref<InvokeCommandActionBase>, value: bool| this.set_pass_event_args_to_command(value)
41 },
42 ],
43 },
44});
45ferroui_base::ferro_impl_classes!(InvokeCommandActionBase: StyledElementImpl, StyledElementActionImpl);
46
47ferro_properties! {
48 impl InvokeCommandActionBase {
49 pub fn command_property() -> StyledProperty<Option<Rc<dyn ICommand>>> {
51 FerroProperty::register::<InvokeCommandActionBase, _>("Command", None)
52 }
53
54 pub fn can_execute_command_property() -> DirectProperty<InvokeCommandActionBase, bool> {
56 FerroProperty::register_direct::<InvokeCommandActionBase, _>(
57 "CanExecuteCommand",
58 |action| action.can_execute_command(),
59 None,
60 false,
61 )
62 }
63
64 pub fn can_execute_command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
66 FerroProperty::register::<InvokeCommandActionBase, _>("CanExecuteCommandParameter", None)
67 }
68
69 pub fn use_command_can_execute_for_is_enabled_property() -> StyledProperty<bool> {
71 FerroProperty::register::<InvokeCommandActionBase, _>("UseCommandCanExecuteForIsEnabled", false)
72 }
73
74 pub fn command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
76 FerroProperty::register::<InvokeCommandActionBase, _>("CommandParameter", None)
77 }
78
79 pub fn input_converter_property() -> StyledProperty<Option<Rc<dyn IValueConverter>>> {
81 FerroProperty::register::<InvokeCommandActionBase, _>("InputConverter", None)
82 }
83
84 pub fn input_converter_parameter_property() -> StyledProperty<Option<BoxedValue>> {
86 FerroProperty::register::<InvokeCommandActionBase, _>("InputConverterParameter", None)
87 }
88
89 pub fn input_converter_language_property() -> StyledProperty<Option<String>> {
92 FerroProperty::register::<InvokeCommandActionBase, _>("InputConverterLanguage", Some(String::new()))
93 }
94 }
95}
96
97ferroui_base::ferro_overrides! {
98 impl FerroObjectImpl for InvokeCommandActionBase {
99 fn constructed(this: &Self) {
101 Self::parent_constructed(this);
102
103 let action = this.to_ref().downgrade();
104 let _ = this.command_can_execute_observer.set(CommandCanExecuteObserver::new(move |value| {
105 if let Some(action) = action.upgrade() {
106 action.set_can_execute_command(value);
107 }
108 }));
109 }
110
111 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
112 Self::parent_on_property_changed(this, change);
113
114 let property = change.property();
115 if property == Self::command_property().as_property()
116 || property == Self::command_parameter_property().as_property()
117 || property == Self::can_execute_command_parameter_property().as_property()
118 || property == Self::input_converter_property().as_property()
119 {
120 this.update_command_can_execute_observer();
121 } else if property == Self::use_command_can_execute_for_is_enabled_property().as_property() {
122 this.update_command_can_execute_is_enabled_binding();
123 }
124 }
125 }
126}
127
128impl InvokeCommandActionBase {
129 fn static_constructor() {
131 <dyn IActionLogicalTreeLifecycle>::register::<InvokeCommandActionBase>();
132 }
133
134 pub fn construct() -> Self {
136 Self {
137 base: StyledElementAction::construct(),
138 command_can_execute_observer: OnceCell::new(),
139 command_can_execute_is_enabled_binder: CommandCanExecuteIsEnabledBinder::new(),
140 can_execute_command: Cell::new(true),
141 pass_event_args_to_command: Cell::new(false),
142 }
143 }
144
145 pub fn command(&self) -> Option<Rc<dyn ICommand>> {
147 self.get_value(Self::command_property())
148 }
149
150 pub fn set_command(&self, value: Option<Rc<dyn ICommand>>) {
151 self.set_value(Self::command_property(), value)
152 }
153
154 pub fn can_execute_command(&self) -> bool {
157 self.can_execute_command.get()
158 }
159
160 fn set_can_execute_command(&self, value: bool) {
161 self.set_and_raise_cell(Self::can_execute_command_property(), &self.can_execute_command, value);
162 }
163
164 pub fn can_execute_command_parameter(&self) -> Option<BoxedValue> {
168 self.get_value(Self::can_execute_command_parameter_property())
169 }
170
171 pub fn set_can_execute_command_parameter(&self, value: Option<BoxedValue>) {
172 self.set_value(Self::can_execute_command_parameter_property(), value)
173 }
174
175 pub fn use_command_can_execute_for_is_enabled(&self) -> bool {
178 self.get_value(Self::use_command_can_execute_for_is_enabled_property())
179 }
180
181 pub fn set_use_command_can_execute_for_is_enabled(&self, value: bool) {
182 self.set_value(Self::use_command_can_execute_for_is_enabled_property(), value)
183 }
184
185 pub fn command_parameter(&self) -> Option<BoxedValue> {
188 self.get_value(Self::command_parameter_property())
189 }
190
191 pub fn set_command_parameter(&self, value: Option<BoxedValue>) {
192 self.set_value(Self::command_parameter_property(), value)
193 }
194
195 pub fn input_converter(&self) -> Option<Rc<dyn IValueConverter>> {
197 self.get_value(Self::input_converter_property())
198 }
199
200 pub fn set_input_converter(&self, value: Option<Rc<dyn IValueConverter>>) {
201 self.set_value(Self::input_converter_property(), value)
202 }
203
204 pub fn input_converter_parameter(&self) -> Option<BoxedValue> {
207 self.get_value(Self::input_converter_parameter_property())
208 }
209
210 pub fn set_input_converter_parameter(&self, value: Option<BoxedValue>) {
211 self.set_value(Self::input_converter_parameter_property(), value)
212 }
213
214 pub fn input_converter_language(&self) -> Option<String> {
217 self.get_value(Self::input_converter_language_property())
218 }
219
220 pub fn set_input_converter_language(&self, value: Option<String>) {
221 self.set_value(Self::input_converter_language_property(), value)
222 }
223
224 pub fn pass_event_args_to_command(&self) -> bool {
227 self.pass_event_args_to_command.get()
228 }
229
230 pub fn set_pass_event_args_to_command(&self, value: bool) {
231 if self.pass_event_args_to_command.get() == value {
232 return;
233 }
234
235 self.pass_event_args_to_command.set(value);
236 self.update_command_can_execute_observer();
237 }
238
239 fn update_command_can_execute_observer(&self) {
240 if let Some(observer) = self.command_can_execute_observer.get() {
241 observer.update_with(self.command(), self.resolve_can_execute_parameter(), self.is_can_execute_parameter_known());
242 }
243 }
244
245 pub fn resolve_parameter(&self, parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
250 if self.is_set(Self::command_parameter_property()) {
251 self.command_parameter()
252 } else if let Some(input_converter) = self.input_converter() {
253 let culture = match self.input_converter_language() {
254 Some(language) => CultureInfo::get_culture_info(&language),
255 None => CultureInfo::current_culture(),
256 };
257 input_converter
258 .convert(parameter, ValueType::object(), self.input_converter_parameter().as_ref(), &culture)
259 .ok()
260 .flatten()
261 } else if self.pass_event_args_to_command() {
262 parameter.cloned()
263 } else {
264 None
265 }
266 }
267
268 fn resolve_can_execute_parameter(&self) -> Option<BoxedValue> {
269 if self.is_set(Self::can_execute_command_parameter_property()) {
270 return self.can_execute_command_parameter();
271 }
272
273 if self.is_set(Self::command_parameter_property()) {
274 self.command_parameter()
275 } else {
276 None
277 }
278 }
279
280 fn is_can_execute_parameter_known(&self) -> bool {
281 self.is_set(Self::can_execute_command_parameter_property())
282 || self.is_set(Self::command_parameter_property())
283 || (self.input_converter().is_none() && !self.pass_event_args_to_command())
284 }
285
286 fn update_command_can_execute_is_enabled_binding(&self) {
287 self.command_can_execute_is_enabled_binder.update(
288 self.resolve_command_can_execute_is_enabled_target(),
289 self.use_command_can_execute_for_is_enabled(),
290 self.get_observable(Self::can_execute_command_property()),
291 );
292 }
293
294 fn resolve_command_can_execute_is_enabled_target(&self) -> Option<Ref<InputElement>> {
297 for logical in self.get_self_and_logical_ancestors() {
298 if let Some(behavior) = <dyn IBehavior>::of(&logical) {
299 if let Some(associated_input_element) =
300 behavior.associated_object().and_then(|associated_object| associated_object.cast::<InputElement>())
301 {
302 return Some(associated_input_element);
303 }
304 }
305
306 if let Some(input_element) = logical.cast::<InputElement>() {
307 return Some(input_element);
308 }
309 }
310
311 None
312 }
313}
314
315impl IActionLogicalTreeLifecycle for InvokeCommandActionBase {
316 fn attached_to_action_logical_tree(&self) {
317 if let Some(observer) = self.command_can_execute_observer.get() {
318 observer.start_with(self.command(), self.resolve_can_execute_parameter(), self.is_can_execute_parameter_known());
319 }
320 self.update_command_can_execute_is_enabled_binding();
321 }
322
323 fn detached_from_action_logical_tree(&self) {
324 self.command_can_execute_is_enabled_binder.stop();
325 if let Some(observer) = self.command_can_execute_observer.get() {
326 observer.stop();
327 }
328 }
329}