ferroui_xaml_interactivity/behaviors/
invoke_command_behavior_base.rs1use 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#[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 pub fn command_property() -> StyledProperty<Option<Rc<dyn ICommand>>> {
48 FerroProperty::register::<InvokeCommandBehaviorBase, _>("Command", None)
49 }
50
51 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 pub fn can_execute_command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
63 FerroProperty::register::<InvokeCommandBehaviorBase, _>("CanExecuteCommandParameter", None)
64 }
65
66 pub fn use_command_can_execute_for_is_enabled_property() -> StyledProperty<bool> {
68 FerroProperty::register::<InvokeCommandBehaviorBase, _>("UseCommandCanExecuteForIsEnabled", false)
69 }
70
71 pub fn command_parameter_property() -> StyledProperty<Option<BoxedValue>> {
73 FerroProperty::register::<InvokeCommandBehaviorBase, _>("CommandParameter", None)
74 }
75
76 pub fn input_converter_property() -> StyledProperty<Option<Rc<dyn IValueConverter>>> {
78 FerroProperty::register::<InvokeCommandBehaviorBase, _>("InputConverter", None)
79 }
80
81 pub fn input_converter_parameter_property() -> StyledProperty<Option<BoxedValue>> {
83 FerroProperty::register::<InvokeCommandBehaviorBase, _>("InputConverterParameter", None)
84 }
85
86 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 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 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 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 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 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 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 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 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 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 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 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 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}