ferroui_base/data/converters/
method_to_command_converter.rs1use crate::data::core::plugins::InpcPropertyAccessor;
2use crate::data::core::{ValueType, ValueTypes};
3use crate::data::model::{Event, ICommand};
4use crate::metadata::{attributes, MarkupAttributeValue, MarkupDelegate, MarkupMethod, MarkupValue};
5use crate::reactive::{Disposable, IDisposable};
6use crate::threading::{Dispatcher, DispatcherPriority};
7use crate::BoxedValue;
8use std::cell::Cell;
9use std::rc::{Rc, Weak};
10
11pub struct MethodToCommandConverter {
33 this: Weak<MethodToCommandConverter>,
34 action: MarkupDelegate,
35 parameter_type: Option<ValueType>,
37 can_execute: Option<&'static MarkupMethod>,
38 dependency_properties: Vec<&'static str>,
39 token: Cell<Option<u64>>,
40 can_execute_changed: Event<()>,
41}
42
43impl MethodToCommandConverter {
44 pub fn new(action: &MarkupDelegate) -> Rc<Self> {
48 let method = action.method();
49 let parameter_type = method.and_then(|method| method.parameters.first()).map(|parameter| parameter());
50
51 let can_execute = method.and_then(|method| {
52 let name = format!("Can{}", method.name);
53 let mut current = action.declaring_type();
54 let mut depth = 0;
55 while let Some(markup) = current {
56 let found = markup
57 .find_methods(&name)
58 .find(|m| m.parameters.len() == 1 && m.parameters[0]().is_object() && m.is_static == method.is_static);
59 if found.is_some() {
60 return found;
61 }
62 depth += 1;
63 current = if depth < 64 { markup.base_type() } else { None };
64 }
65 None
66 });
67 let dependency_properties: Vec<&'static str> = can_execute
68 .map(|can_execute| {
69 can_execute
70 .attributes
71 .iter()
72 .filter(|attribute| attribute.name == attributes::DEPENDS_ON)
73 .filter_map(|attribute| match attribute.arguments.first() {
74 Some(MarkupAttributeValue::Str(name)) => Some(*name),
75 _ => None,
76 })
77 .collect()
78 })
79 .unwrap_or_default();
80
81 let this = Rc::new_cyclic(|this: &Weak<Self>| Self {
82 this: this.clone(),
83 action: action.clone(),
84 parameter_type,
85 can_execute,
86 dependency_properties,
87 token: Cell::new(None),
88 can_execute_changed: Event::new(),
89 });
90
91 if !this.dependency_properties.is_empty() {
92 let target = action.target();
93 if let Some(inpc) = target.as_ref().and_then(|target| InpcPropertyAccessor::find_notifier(&**target)) {
94 let weak = this.this.clone();
96 let token = inpc.property_changed().add(Rc::new(move |name: &str| {
97 if let Some(this) = weak.upgrade() {
98 this.on_property_changed(name);
99 }
100 }));
101 this.token.set(Some(token));
102 }
103 }
104 this
105 }
106
107 fn on_property_changed(&self, name: &str) {
108 if name.trim().is_empty() || self.dependency_properties.contains(&name) {
109 let weak = self.this.clone();
110 Dispatcher::current_dispatcher().post_local(
111 move || {
112 if let Some(this) = weak.upgrade() {
113 this.can_execute_changed.raise(&());
114 }
115 },
116 DispatcherPriority::INPUT,
117 );
118 }
119 }
120
121 fn arguments(&self, parameter: Option<&BoxedValue>) -> Vec<MarkupValue> {
122 match self.parameter_type {
123 None => Vec::new(),
124 Some(type_) if type_.is_object() => vec![parameter.cloned()],
125 Some(type_) => vec![ValueTypes::try_convert(parameter, type_).flatten()],
127 }
128 }
129}
130
131impl ICommand for MethodToCommandConverter {
132 fn can_execute(&self, parameter: Option<&BoxedValue>) -> bool {
133 if !self.action.is_target_alive() {
134 return false;
135 }
136 let Some(can_execute) = self.can_execute else { return true };
137 let result = match can_execute.is_static {
138 true => (can_execute.invoke)(&[parameter.cloned()]),
139 false => (can_execute.invoke)(&[self.action.target(), parameter.cloned()]),
140 };
141 match result {
142 Ok(value) => crate::metadata::from_markup_value::<bool>(&value).unwrap_or(false),
143 Err(error) => panic!("{}: {error}", can_execute.name),
144 }
145 }
146
147 fn execute(&self, parameter: Option<&BoxedValue>) {
148 let Some(method) = self.action.method() else {
149 self.action.invoke(&[parameter.cloned()]);
150 return;
151 };
152 if !self.action.is_target_alive() {
153 return;
154 }
155 if let Err(error) = self.action.try_invoke(&self.arguments(parameter)) {
156 panic!("{}: {error}", method.name);
157 }
158 }
159
160 fn can_execute_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
161 let token = self.can_execute_changed.add(Rc::new(move |_: &()| handler()));
162 let weak = self.this.clone();
163 Disposable::create(move || {
164 if let Some(this) = weak.upgrade() {
165 this.can_execute_changed.remove(token);
166 }
167 })
168 }
169}
170
171impl Drop for MethodToCommandConverter {
172 fn drop(&mut self) {
173 if let Some(token) = self.token.take() {
174 let target = self.action.target();
175 if let Some(inpc) = target.as_ref().and_then(|target| InpcPropertyAccessor::find_notifier(&**target)) {
176 inpc.property_changed().remove(token);
177 }
178 }
179 }
180}
181
182pub(crate) fn method_to_command(value: &BoxedValue, target_type: ValueType) -> Option<BoxedValue> {
187 let nullable = target_type.is::<Option<Rc<dyn ICommand>>>();
188 if !nullable && !target_type.is::<Rc<dyn ICommand>>() {
189 return None;
190 }
191 let delegate = value.downcast_ref::<MarkupDelegate>()?;
192 if delegate.method()?.parameters.len() > 1 {
193 return None;
194 }
195 let command: Rc<dyn ICommand> = MethodToCommandConverter::new(delegate);
196 Some(match nullable {
197 true => Rc::new(Some(command)),
198 false => Rc::new(command),
199 })
200}