Skip to main content

ferroui_reactiveui/binding/command_binding/
creates_command_binding_via_command_parameter.rs

1//! `CreatesCommandBindingViaCommandParameter`: binds a command to the `Command` and
2//! `CommandParameter` properties of a control.
3//!
4//! Not an upstream file of ReactiveUI.Binding: ReactiveUI had this binder, which found the
5//! properties by reflection; ReactiveUI.Binding writes the binding at compile time
6//! (`CommandPropertyBindingPlugin`). Here the properties of a control type are given as paths
7//! (to read them) and setters, and the binder is registered in the service locator as a
8//! `dyn ICreatesCommandBinding`.
9
10use crate::binding::bindings::BindingAffinity;
11use crate::binding::interfaces::{CommandEventSubscription, CommandParameter, ICreatesCommandBinding};
12use crate::reactive_command::ICommand;
13use ferroui_reactive::disposables::{CompositeDisposable, Disposable};
14use ferroui_reactive::ObservableExt;
15use ferroui_system::component_model::PropertyPath;
16use ferroui_system::{IDisposable, IObservable};
17use std::any::{Any, TypeId};
18use std::fmt;
19use std::rc::Rc;
20
21/// Binds a command to a control of type `C` that has a `Command` and a `CommandParameter`
22/// property (`CreatesCommandBindingViaCommandParameter`): the binding assigns the parameter, then
23/// the command, and disposing it restores the values the control had.
24///
25/// The affinity is [`BindingAffinity::EXPLICIT`] for a control of type `C` when no event is named,
26/// and zero otherwise: this binder binds no event.
27pub struct CreatesCommandBindingViaCommandParameter<C> {
28    command: PropertyPath<Rc<C>, Option<Rc<dyn ICommand>>>,
29    set_command: Rc<dyn Fn(&C, Option<Rc<dyn ICommand>>)>,
30    command_parameter: PropertyPath<Rc<C>, CommandParameter>,
31    set_command_parameter: Rc<dyn Fn(&C, CommandParameter)>,
32}
33
34impl<C> fmt::Debug for CreatesCommandBindingViaCommandParameter<C> {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        f.debug_struct("CreatesCommandBindingViaCommandParameter").finish_non_exhaustive()
37    }
38}
39
40impl<C: 'static> CreatesCommandBindingViaCommandParameter<C> {
41    /// A binder over the `Command` property (`command` reads it, `set_command` assigns it) and the
42    /// `CommandParameter` property of the controls of type `C`.
43    pub fn new(
44        command: PropertyPath<Rc<C>, Option<Rc<dyn ICommand>>>,
45        set_command: impl Fn(&C, Option<Rc<dyn ICommand>>) + 'static,
46        command_parameter: PropertyPath<Rc<C>, CommandParameter>,
47        set_command_parameter: impl Fn(&C, CommandParameter) + 'static,
48    ) -> Self {
49        Self { command, set_command: Rc::new(set_command), command_parameter, set_command_parameter: Rc::new(set_command_parameter) }
50    }
51}
52
53impl<C: 'static> ICreatesCommandBinding for CreatesCommandBindingViaCommandParameter<C> {
54    fn get_affinity_for_object(&self, target_type: TypeId, has_event_target: bool) -> i32 {
55        if has_event_target || target_type != TypeId::of::<C>() {
56            return 0;
57        }
58        BindingAffinity::EXPLICIT
59    }
60
61    fn bind_command_to_object(
62        &self,
63        command: Option<Rc<dyn ICommand>>,
64        target: Option<Rc<dyn Any>>,
65        command_parameter: Rc<dyn IObservable<CommandParameter>>,
66    ) -> Option<Rc<dyn IDisposable>> {
67        let command = command?;
68        let control = target?.downcast::<C>().ok()?;
69
70        // The values the control had, restored when the binding ends.
71        let original_command = self.command.get(&control);
72        let original_parameter = self.command_parameter.get(&control);
73
74        // The parameter is assigned first, so that the control never has the command without it.
75        let set_parameter = self.set_command_parameter.clone();
76        let parameter_control = control.clone();
77        let parameters = command_parameter.subscribe_next(move |parameter| set_parameter(&parameter_control, parameter));
78        (self.set_command)(&control, Some(command));
79
80        let set_command = self.set_command.clone();
81        let restore_parameter = self.set_command_parameter.clone();
82        let restore = Disposable::create(move || {
83            set_command(&control, original_command);
84            restore_parameter(&control, original_parameter);
85        });
86        Some(CompositeDisposable::from_disposables([parameters, restore]))
87    }
88
89    /// `None`: this binder binds no event.
90    fn bind_command_to_object_event(
91        &self,
92        _command: Option<Rc<dyn ICommand>>,
93        _target: Option<Rc<dyn Any>>,
94        _command_parameter: Rc<dyn IObservable<CommandParameter>>,
95        _event_name: &str,
96    ) -> Option<Rc<dyn IDisposable>> {
97        None
98    }
99
100    /// `None`: this binder binds no event.
101    fn bind_command_to_object_with_handlers(
102        &self,
103        _command: Option<Rc<dyn ICommand>>,
104        _target: Option<Rc<dyn Any>>,
105        _command_parameter: Rc<dyn IObservable<CommandParameter>>,
106        _subscription: CommandEventSubscription,
107    ) -> Option<Rc<dyn IDisposable>> {
108        None
109    }
110}