Skip to main content

ferroui_xaml_interactivity/behaviors/
invoke_command_behavior_base.rs

1//! Port of `Behaviors/InvokeCommandBehaviorBase.cs`.
2
3use crate::helpers::{CommandCanExecuteIsEnabledBinder, CommandCanExecuteObserver};
4use crate::{StyledElementBehaviorImpl, StyledElementBehaviorImplExt, StyledElementBehaviorOfT};
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, StaticType, StyledElementImpl, StyledProperty,
12};
13use ferroui_controls::Control;
14use std::cell::{Cell, OnceCell};
15use std::rc::Rc;
16
17/// Invoke command behavior base class: a behavior of a [`Control`] that invokes a command,
18/// reports whether the command can execute ([`can_execute_command`](Self::can_execute_command))
19/// and can make the control follow it ([`use_command_can_execute_for_is_enabled`](Self::use_command_can_execute_for_is_enabled)).
20///
21/// The class is abstract.
22#[repr(C)]
23pub struct InvokeCommandBehaviorBase {
24    base: StyledElementBehaviorOfT,
25    command_can_execute_observer: OnceCell<CommandCanExecuteObserver>,
26    command_can_execute_is_enabled_binder: CommandCanExecuteIsEnabledBinder,
27    can_execute_command: Cell<bool>,
28    pass_event_args_to_command: Cell<bool>,
29}
30
31ferro_class!(InvokeCommandBehaviorBase: StyledElementBehaviorOfT);
32ferro_class_info!(InvokeCommandBehaviorBase {
33    markup: {
34        properties: [
35            PassEventArgsToCommand: bool {
36                get: |this: &Ref<InvokeCommandBehaviorBase>| this.pass_event_args_to_command(),
37                set: |this: &Ref<InvokeCommandBehaviorBase>, value: bool| this.set_pass_event_args_to_command(value)
38            },
39        ],
40    },
41});
42ferroui_base::ferro_impl_classes!(InvokeCommandBehaviorBase: StyledElementImpl);
43
44ferro_properties! {
45    impl InvokeCommandBehaviorBase {
46        /// Identifies the `Command` property.
47        pub fn command_property() -> StyledProperty<Option<Rc<dyn ICommand>>> {
48            FerroProperty::register::<InvokeCommandBehaviorBase, _>("Command", None)
49        }
50
51        /// Identifies the `CanExecuteCommand` property.
52        pub fn can_execute_command_property() -> DirectProperty<InvokeCommandBehaviorBase, bool> {
53            FerroProperty::register_direct::<InvokeCommandBehaviorBase, _>(
54                "CanExecuteCommand",
55                |behavior| behavior.can_execute_command(),
56                None,
57                false,
58            )
59        }
60
61        /// Identifies the `CanExecuteCommandParameter` property.
62        pub fn can_execute_command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
63            FerroProperty::register::<InvokeCommandBehaviorBase, _>("CanExecuteCommandParameter", None)
64        }
65
66        /// Identifies the `UseCommandCanExecuteForIsEnabled` property.
67        pub fn use_command_can_execute_for_is_enabled_property() -> StyledProperty<bool> {
68            FerroProperty::register::<InvokeCommandBehaviorBase, _>("UseCommandCanExecuteForIsEnabled", false)
69        }
70
71        /// Identifies the `CommandParameter` property.
72        pub fn command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
73            FerroProperty::register::<InvokeCommandBehaviorBase, _>("CommandParameter", None)
74        }
75
76        /// Identifies the `InputConverter` property.
77        pub fn input_converter_property() -> StyledProperty<Option<Rc<dyn IValueConverter>>> {
78            FerroProperty::register::<InvokeCommandBehaviorBase, _>("InputConverter", None)
79        }
80
81        /// Identifies the `InputConverterParameter` property.
82        pub fn input_converter_parameter_property() -> StyledProperty<Option<BoxedValue>> {
83            FerroProperty::register::<InvokeCommandBehaviorBase, _>("InputConverterParameter", None)
84        }
85
86        /// Identifies the `InputConverterLanguage` property. The empty string of the default
87        /// value means the invariant culture.
88        pub fn input_converter_language_property() -> StyledProperty<Option<String>> {
89            FerroProperty::register::<InvokeCommandBehaviorBase, _>("InputConverterLanguage", Some(String::new()))
90        }
91    }
92}
93
94ferroui_base::ferro_overrides! {
95    impl FerroObjectImpl for InvokeCommandBehaviorBase {
96        /// The constructor body: the observer reports to the `CanExecuteCommand` property.
97        fn constructed(this: &Self) {
98            Self::parent_constructed(this);
99
100            let behavior = this.to_ref().downgrade();
101            let _ = this.command_can_execute_observer.set(CommandCanExecuteObserver::new(move |value| {
102                if let Some(behavior) = behavior.upgrade() {
103                    behavior.set_can_execute_command(value);
104                }
105            }));
106        }
107
108        fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
109            Self::parent_on_property_changed(this, change);
110
111            let property = change.property();
112            if property == Self::command_property().as_property()
113                || property == Self::command_parameter_property().as_property()
114                || property == Self::can_execute_command_parameter_property().as_property()
115                || property == Self::input_converter_property().as_property()
116            {
117                this.update_command_can_execute_observer();
118            } else if property == Self::use_command_can_execute_for_is_enabled_property().as_property() {
119                this.update_command_can_execute_is_enabled_binding();
120            }
121        }
122    }
123}
124
125ferroui_base::ferro_overrides! {
126    impl StyledElementBehaviorImpl for InvokeCommandBehaviorBase {
127        fn on_attached(this: &Self) {
128            Self::parent_on_attached(this);
129
130            if let Some(observer) = this.command_can_execute_observer.get() {
131                observer.start_with(this.command(), this.resolve_can_execute_parameter(), this.is_can_execute_parameter_known());
132            }
133            this.update_command_can_execute_is_enabled_binding();
134        }
135
136        fn on_detaching(this: &Self) {
137            this.command_can_execute_is_enabled_binder.stop();
138            if let Some(observer) = this.command_can_execute_observer.get() {
139                observer.stop();
140            }
141
142            Self::parent_on_detaching(this);
143        }
144    }
145}
146
147impl InvokeCommandBehaviorBase {
148    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
149    pub fn construct() -> Self {
150        Self {
151            base: StyledElementBehaviorOfT::construct(<Control as StaticType>::TYPE),
152            command_can_execute_observer: OnceCell::new(),
153            command_can_execute_is_enabled_binder: CommandCanExecuteIsEnabledBinder::new(),
154            can_execute_command: Cell::new(true),
155            pass_event_args_to_command: Cell::new(false),
156        }
157    }
158
159    /// Gets or sets the command this behavior should invoke.
160    pub fn command(&self) -> Option<Rc<dyn ICommand>> {
161        self.get_value(Self::command_property())
162    }
163
164    pub fn set_command(&self, value: Option<Rc<dyn ICommand>>) {
165        self.set_value(Self::command_property(), value)
166    }
167
168    /// Gets a value indicating whether [`command`](Self::command) can execute with the current
169    /// can-execute parameter.
170    pub fn can_execute_command(&self) -> bool {
171        self.can_execute_command.get()
172    }
173
174    fn set_can_execute_command(&self, value: bool) {
175        self.set_and_raise_cell(Self::can_execute_command_property(), &self.can_execute_command, value);
176    }
177
178    /// Gets or sets the parameter that is passed to the `can_execute` of the command. When it is
179    /// not set, [`command_parameter`](Self::command_parameter) is used if it is set. It does not
180    /// change the parameter passed to the `execute` of the command.
181    pub fn can_execute_command_parameter(&self) -> Option<BoxedValue> {
182        self.get_value(Self::can_execute_command_parameter_property())
183    }
184
185    pub fn set_can_execute_command_parameter(&self, value: Option<BoxedValue>) {
186        self.set_value(Self::can_execute_command_parameter_property(), value)
187    }
188
189    /// Gets or sets a value indicating whether the `IsEnabled` property of the associated control
190    /// should follow [`can_execute_command`](Self::can_execute_command).
191    pub fn use_command_can_execute_for_is_enabled(&self) -> bool {
192        self.get_value(Self::use_command_can_execute_for_is_enabled_property())
193    }
194
195    pub fn set_use_command_can_execute_for_is_enabled(&self, value: bool) {
196        self.set_value(Self::use_command_can_execute_for_is_enabled_property(), value)
197    }
198
199    /// Gets or sets the parameter that is passed to the `execute` of the command. If this is not
200    /// set, the parameter given to the behavior is used.
201    pub fn command_parameter(&self) -> Option<BoxedValue> {
202        self.get_value(Self::command_parameter_property())
203    }
204
205    pub fn set_command_parameter(&self, value: Option<BoxedValue>) {
206        self.set_value(Self::command_parameter_property(), value)
207    }
208
209    /// Gets or sets the converter that is run on the parameter given to the behavior.
210    pub fn input_converter(&self) -> Option<Rc<dyn IValueConverter>> {
211        self.get_value(Self::input_converter_property())
212    }
213
214    pub fn set_input_converter(&self, value: Option<Rc<dyn IValueConverter>>) {
215        self.set_value(Self::input_converter_property(), value)
216    }
217
218    /// Gets or sets the parameter that is passed to the `convert` of the
219    /// [`input_converter`](Self::input_converter).
220    pub fn input_converter_parameter(&self) -> Option<BoxedValue> {
221        self.get_value(Self::input_converter_parameter_property())
222    }
223
224    pub fn set_input_converter_parameter(&self, value: Option<BoxedValue>) {
225        self.set_value(Self::input_converter_parameter_property(), value)
226    }
227
228    /// Gets or sets the language that is passed to the `convert` of the
229    /// [`input_converter`](Self::input_converter).
230    pub fn input_converter_language(&self) -> Option<String> {
231        self.get_value(Self::input_converter_language_property())
232    }
233
234    pub fn set_input_converter_language(&self, value: Option<String>) {
235        self.set_value(Self::input_converter_language_property(), value)
236    }
237
238    /// Specifies whether the event args of the event that triggered this behavior should be passed
239    /// to the command as a parameter.
240    pub fn pass_event_args_to_command(&self) -> bool {
241        self.pass_event_args_to_command.get()
242    }
243
244    pub fn set_pass_event_args_to_command(&self, value: bool) {
245        if self.pass_event_args_to_command.get() == value {
246            return;
247        }
248
249        self.pass_event_args_to_command.set(value);
250        self.update_command_can_execute_observer();
251    }
252
253    fn update_command_can_execute_observer(&self) {
254        if let Some(observer) = self.command_can_execute_observer.get() {
255            observer.update_with(self.command(), self.resolve_can_execute_parameter(), self.is_can_execute_parameter_known());
256        }
257    }
258
259    /// Resolves the command parameter that will be supplied to the command: the
260    /// [`command_parameter`](Self::command_parameter) if it is set, else the parameter given to
261    /// the behavior converted by the [`input_converter`](Self::input_converter), else the
262    /// parameter itself if [`pass_event_args_to_command`](Self::pass_event_args_to_command).
263    pub fn resolve_parameter(&self, parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
264        if self.is_set(Self::command_parameter_property()) {
265            self.command_parameter()
266        } else if let Some(input_converter) = self.input_converter() {
267            let culture = match self.input_converter_language() {
268                Some(language) => CultureInfo::get_culture_info(&language),
269                None => CultureInfo::current_culture(),
270            };
271            input_converter
272                .convert(parameter, ValueType::object(), self.input_converter_parameter().as_ref(), &culture)
273                .ok()
274                .flatten()
275        } else if self.pass_event_args_to_command() {
276            parameter.cloned()
277        } else {
278            None
279        }
280    }
281
282    fn resolve_can_execute_parameter(&self) -> Option<BoxedValue> {
283        if self.is_set(Self::can_execute_command_parameter_property()) {
284            return self.can_execute_command_parameter();
285        }
286
287        if self.is_set(Self::command_parameter_property()) {
288            self.command_parameter()
289        } else {
290            None
291        }
292    }
293
294    fn is_can_execute_parameter_known(&self) -> bool {
295        self.is_set(Self::can_execute_command_parameter_property())
296            || self.is_set(Self::command_parameter_property())
297            || (self.input_converter().is_none() && !self.pass_event_args_to_command())
298    }
299
300    fn update_command_can_execute_is_enabled_binding(&self) {
301        self.command_can_execute_is_enabled_binder.update(
302            self.associated_object_as::<InputElement>(),
303            self.use_command_can_execute_for_is_enabled(),
304            self.get_observable(Self::can_execute_command_property()),
305        );
306    }
307}