Skip to main content

ferroui_reactiveui/binding/command_binding/
command_invoker.rs

1//! `CommandInvoker` (ReactiveUI.Binding, `CommandBinding/CommandInvoker.cs`): executes a command
2//! with each value of an observable, which is what `InvokeCommand` runs.
3
4use crate::reactive_command::ICommand;
5use ferroui_reactive::disposables::CompositeDisposable;
6use ferroui_system::{IDisposable, IObservable, IObserver, ObservableError};
7use std::any::Any;
8use std::cell::RefCell;
9use std::rc::Rc;
10
11/// Executes a command with each value of an observable as its parameter, skipping a value the
12/// command cannot execute (`CommandInvoker`).
13///
14/// An error of the values, or of the observable of commands, is rethrown where it is raised
15/// (`ExceptionDispatchInfo.Throw`): here it panics with its message.
16///
17/// Deviation (DEVIATIONS.md, ReactiveUI): the overload on an observable of commands is
18/// [`invoke_observed`](Self::invoke_observed); the null checks of the arguments have no
19/// counterpart.
20pub struct CommandInvoker;
21
22impl CommandInvoker {
23    /// Executes `command` with each value of `source` that it can execute
24    /// (`Invoke<T>(IObservable<T>, ICommand)`); disposing the result stops it.
25    pub fn invoke<T: 'static>(source: &Rc<dyn IObservable<T>>, command: Rc<dyn ICommand>) -> Rc<dyn IDisposable> {
26        source.subscribe(Rc::new(FixedCommandObserver { command }))
27    }
28
29    /// Executes the latest command of `commands` with each value of `source` that it can execute,
30    /// and nothing while there is none (`Invoke<T>(IObservable<T>, IObservable<ICommand?>)`);
31    /// disposing the result ends both subscriptions.
32    pub fn invoke_observed<T: 'static>(
33        source: &Rc<dyn IObservable<T>>,
34        commands: &Rc<dyn IObservable<Option<Rc<dyn ICommand>>>>,
35    ) -> Rc<dyn IDisposable> {
36        let latch = Rc::new(CommandLatch { command: RefCell::new(None) });
37        let command_subscription = commands.subscribe(latch.clone());
38        let source_subscription = source.subscribe(Rc::new(LatchedCommandObserver { latch }));
39        CompositeDisposable::from_disposables([command_subscription, source_subscription])
40    }
41}
42
43/// Rethrows an error where it was raised (`ExceptionDispatchInfo.Capture(error).Throw()`).
44fn rethrow(error: ObservableError) -> ! {
45    panic!("{error}")
46}
47
48/// `FixedCommandObserver<T>`.
49struct FixedCommandObserver {
50    command: Rc<dyn ICommand>,
51}
52
53impl<T: 'static> IObserver<T> for FixedCommandObserver {
54    fn on_next(&self, value: T) {
55        let parameter: &dyn Any = &value;
56        if !self.command.can_execute(Some(parameter)) {
57            return;
58        }
59        self.command.execute(Some(parameter));
60    }
61
62    fn on_error(&self, error: ObservableError) {
63        rethrow(error);
64    }
65
66    fn on_completed(&self) {}
67}
68
69/// `CommandLatch`: the latest command of the observable of commands.
70struct CommandLatch {
71    command: RefCell<Option<Rc<dyn ICommand>>>,
72}
73
74impl IObserver<Option<Rc<dyn ICommand>>> for CommandLatch {
75    fn on_next(&self, value: Option<Rc<dyn ICommand>>) {
76        *self.command.borrow_mut() = value;
77    }
78
79    fn on_error(&self, error: ObservableError) {
80        rethrow(error);
81    }
82
83    fn on_completed(&self) {}
84}
85
86/// `LatchedCommandObserver<T>`.
87struct LatchedCommandObserver {
88    latch: Rc<CommandLatch>,
89}
90
91impl<T: 'static> IObserver<T> for LatchedCommandObserver {
92    fn on_next(&self, value: T) {
93        let Some(command) = self.latch.command.borrow().clone() else { return };
94        let parameter: &dyn Any = &value;
95        if !command.can_execute(Some(parameter)) {
96            return;
97        }
98        command.execute(Some(parameter));
99    }
100
101    fn on_error(&self, error: ObservableError) {
102        rethrow(error);
103    }
104
105    fn on_completed(&self) {}
106}