Skip to main content

ferroui_reactiveui/binding/command_binding/
creates_command_binding_via_event.rs

1//! `CreatesCommandBindingViaEvent`: binds a command to an event of a control.
2//!
3//! Not an upstream file of ReactiveUI.Binding: ReactiveUI had this binder, which found the event by
4//! reflection; ReactiveUI.Binding writes the binding to an event at compile time
5//! (`DefaultEventBindingPlugin`, `CommandEventBindingEmitter`). Here the events of a control type
6//! are registered by name with accessors, and the binder is registered in the service locator as a
7//! `dyn ICreatesCommandBinding`.
8
9use crate::binding::bindings::BindingAffinity;
10use crate::binding::interfaces::{CommandEventSubscription, CommandParameter, ICreatesCommandBinding};
11use crate::reactive_command::ICommand;
12use ferroui_reactive::disposables::{CompositeDisposable, Disposable};
13use ferroui_reactive::ObservableExt;
14use ferroui_system::events::Event;
15use ferroui_system::{IDisposable, IObservable};
16use std::any::{Any, TypeId};
17use std::cell::RefCell;
18use std::fmt;
19use std::rc::Rc;
20
21/// The names of the events a control raises when it is used, in order of preference, which a
22/// command binds to when no event is named (`defaultEventsToBind`).
23pub const DEFAULT_COMMAND_EVENTS: [&str; 3] = ["Click", "TouchUpInside", "MouseUp"];
24
25/// Adds a handler to an event of a control of type `C`; disposing the result removes it.
26pub type ControlEventSubscription<C> = Rc<dyn Fn(&Rc<C>, Rc<dyn Fn()>) -> Rc<dyn IDisposable>>;
27
28/// A subscription to the event that `event` reads from a control.
29pub fn control_event<C: 'static, A: ?Sized + 'static>(event: fn(&C) -> &Event<A>) -> ControlEventSubscription<C> {
30    Rc::new(move |control: &Rc<C>, handler: Rc<dyn Fn()>| {
31        let token = event(control).add(Rc::new(move |_: &A| handler()));
32        let control = control.clone();
33        Disposable::create(move || {
34            event(&control).remove(token);
35        })
36    })
37}
38
39/// Executes `command` with the latest value of `command_parameter` whenever the event that
40/// `subscribe` adds a handler to is raised, if the command can execute with it; disposing the
41/// result removes the handler and ends the subscription of the parameter.
42pub fn bind_command_to_event(
43    command: Rc<dyn ICommand>,
44    command_parameter: &Rc<dyn IObservable<CommandParameter>>,
45    subscribe: impl FnOnce(Rc<dyn Fn()>) -> Rc<dyn IDisposable>,
46) -> Rc<dyn IDisposable> {
47    let latest: Rc<RefCell<CommandParameter>> = Rc::new(RefCell::new(None));
48    let latest_for_parameter = latest.clone();
49    let parameters = command_parameter.subscribe_next(move |parameter| *latest_for_parameter.borrow_mut() = parameter);
50    let handler = subscribe(Rc::new(move || {
51        let parameter = latest.borrow().clone();
52        if !command.can_execute(parameter.as_deref()) {
53            return;
54        }
55        command.execute(parameter.as_deref());
56    }));
57    CompositeDisposable::from_disposables([handler, parameters])
58}
59
60/// Binds a command to an event of a control of type `C` (`CreatesCommandBindingViaEvent`):
61/// executed with the latest command parameter each time the event is raised, if the command can
62/// execute with it.
63///
64/// The events are registered by name ([`with_event`](Self::with_event)). Without an event name,
65/// the command binds to the first of [`DEFAULT_COMMAND_EVENTS`] that is registered, then to the
66/// first registered event.
67///
68/// The affinity is [`BindingAffinity::EXPLICIT`] for a named event and
69/// [`BindingAffinity::DEFAULT_EVENT`] for a default event, for a control of type `C` only.
70pub struct CreatesCommandBindingViaEvent<C> {
71    events: Vec<(String, ControlEventSubscription<C>)>,
72}
73
74impl<C> fmt::Debug for CreatesCommandBindingViaEvent<C> {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        f.debug_struct("CreatesCommandBindingViaEvent").field("events", &self.events.iter().map(|(name, _)| name).collect::<Vec<_>>()).finish()
77    }
78}
79
80impl<C: 'static> Default for CreatesCommandBindingViaEvent<C> {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl<C: 'static> CreatesCommandBindingViaEvent<C> {
87    /// A binder with no event.
88    pub fn new() -> Self {
89        Self { events: Vec::new() }
90    }
91
92    /// Registers the event named `name` that `event` reads from a control.
93    pub fn with_event<A: ?Sized + 'static>(mut self, name: impl Into<String>, event: fn(&C) -> &Event<A>) -> Self {
94        self.events.push((name.into(), control_event(event)));
95        self
96    }
97
98    /// Registers the event named `name` that `subscription` adds a handler to.
99    pub fn with_event_subscription(mut self, name: impl Into<String>, subscription: ControlEventSubscription<C>) -> Self {
100        self.events.push((name.into(), subscription));
101        self
102    }
103
104    /// The event named `name`.
105    fn event(&self, name: &str) -> Option<&ControlEventSubscription<C>> {
106        self.events.iter().find(|(event_name, _)| event_name == name).map(|(_, event)| event)
107    }
108
109    /// The event bound when none is named.
110    fn default_event(&self) -> Option<&ControlEventSubscription<C>> {
111        DEFAULT_COMMAND_EVENTS.iter().find_map(|name| self.event(name)).or_else(|| self.events.first().map(|(_, event)| event))
112    }
113
114    /// Binds `command` to `event` of `target`, a control of type `C`.
115    fn bind(
116        command: Option<Rc<dyn ICommand>>,
117        target: Option<Rc<dyn Any>>,
118        command_parameter: Rc<dyn IObservable<CommandParameter>>,
119        event: &ControlEventSubscription<C>,
120    ) -> Option<Rc<dyn IDisposable>> {
121        let command = command?;
122        let control = target?.downcast::<C>().ok()?;
123        let event = event.clone();
124        Some(bind_command_to_event(command, &command_parameter, move |handler| event(&control, handler)))
125    }
126}
127
128impl<C: 'static> ICreatesCommandBinding for CreatesCommandBindingViaEvent<C> {
129    fn get_affinity_for_object(&self, target_type: TypeId, has_event_target: bool) -> i32 {
130        if target_type != TypeId::of::<C>() {
131            return 0;
132        }
133        if has_event_target {
134            return if self.events.is_empty() { 0 } else { BindingAffinity::EXPLICIT };
135        }
136        if self.default_event().is_some() {
137            BindingAffinity::DEFAULT_EVENT
138        } else {
139            0
140        }
141    }
142
143    fn bind_command_to_object(
144        &self,
145        command: Option<Rc<dyn ICommand>>,
146        target: Option<Rc<dyn Any>>,
147        command_parameter: Rc<dyn IObservable<CommandParameter>>,
148    ) -> Option<Rc<dyn IDisposable>> {
149        let event = self.default_event()?;
150        Self::bind(command, target, command_parameter, event)
151    }
152
153    fn bind_command_to_object_event(
154        &self,
155        command: Option<Rc<dyn ICommand>>,
156        target: Option<Rc<dyn Any>>,
157        command_parameter: Rc<dyn IObservable<CommandParameter>>,
158        event_name: &str,
159    ) -> Option<Rc<dyn IDisposable>> {
160        let event = self.event(event_name)?;
161        Self::bind(command, target, command_parameter, event)
162    }
163
164    fn bind_command_to_object_with_handlers(
165        &self,
166        command: Option<Rc<dyn ICommand>>,
167        target: Option<Rc<dyn Any>>,
168        command_parameter: Rc<dyn IObservable<CommandParameter>>,
169        subscription: CommandEventSubscription,
170    ) -> Option<Rc<dyn IDisposable>> {
171        let command = command?;
172        target?;
173        Some(bind_command_to_event(command, &command_parameter, move |handler| subscription(handler)))
174    }
175}