Skip to main content

ferroui_reactiveui_controls/
reactive_command_adapter.rs

1//! `ReactiveCommandAdapter`: a command of ReactiveUI as a command of FerroUI, and the conversion of
2//! the command parameters between the two.
3//!
4//! Not an upstream file. Upstream has one `System.Windows.Input.ICommand`, which both ReactiveUI's
5//! commands and the controls' `Command` property use. The port has two: the `ICommand` of
6//! `ferroui-reactiveui`, whose parameter is `Option<&dyn Any>`, and the `ICommand` of the controls
7//! (`ferroui_base::input::ICommand`), whose parameter is a [`BoxedValue`] (a value that compares).
8//! The adapter is the second over the first, so that a `ReactiveCommand` can be the `Command` of a
9//! button.
10
11use ferroui_base::input::ICommand;
12use ferroui_base::{AnyValue, BoxedValue};
13use ferroui_reactiveui::CommandParameter;
14use ferroui_system::IDisposable;
15use std::any::Any;
16use std::fmt;
17use std::rc::Rc;
18
19/// A command of ReactiveUI as a command of FerroUI: each member forwards to the command it wraps,
20/// with the parameter seen as `&dyn Any`.
21///
22/// Deviation (DEVIATIONS.md, ReactiveUI controls): upstream sets the command itself as the
23/// `Command` of a control; the two `ICommand`s of the port are bridged by this adapter.
24pub struct ReactiveCommandAdapter {
25    command: Rc<dyn ferroui_reactiveui::ICommand>,
26}
27
28impl fmt::Debug for ReactiveCommandAdapter {
29    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30        f.debug_struct("ReactiveCommandAdapter").field("command", &(Rc::as_ptr(&self.command) as *const ())).finish()
31    }
32}
33
34impl ReactiveCommandAdapter {
35    /// The command of FerroUI that runs `command`.
36    pub fn new(command: Rc<dyn ferroui_reactiveui::ICommand>) -> Rc<Self> {
37        Rc::new(Self { command })
38    }
39
40    /// [`new`](Self::new) as the handle the `Command` property of a control holds.
41    pub fn command_of(command: Rc<dyn ferroui_reactiveui::ICommand>) -> Rc<dyn ICommand> {
42        Self::new(command)
43    }
44
45    /// The command of ReactiveUI the adapter runs.
46    pub fn command(&self) -> &Rc<dyn ferroui_reactiveui::ICommand> {
47        &self.command
48    }
49
50    /// Whether the adapter runs `command` (the same object).
51    pub fn wraps(&self, command: &Rc<dyn ferroui_reactiveui::ICommand>) -> bool {
52        std::ptr::addr_eq(Rc::as_ptr(&self.command), Rc::as_ptr(command))
53    }
54}
55
56impl ICommand for ReactiveCommandAdapter {
57    fn can_execute(&self, parameter: Option<&BoxedValue>) -> bool {
58        self.command.can_execute(parameter.map(parameter_as_any))
59    }
60
61    fn execute(&self, parameter: Option<&BoxedValue>) {
62        self.command.execute(parameter.map(parameter_as_any));
63    }
64
65    fn can_execute_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
66        self.command.can_execute_changed(handler)
67    }
68}
69
70/// A command parameter of ReactiveUI that is not one of the values the property system compares,
71/// held by a control's `CommandParameter`: it compares by identity, as an object does upstream.
72#[derive(Clone)]
73pub struct ReactiveCommandParameter(pub Rc<dyn Any>);
74
75impl PartialEq for ReactiveCommandParameter {
76    fn eq(&self, other: &Self) -> bool {
77        std::ptr::addr_eq(Rc::as_ptr(&self.0), Rc::as_ptr(&other.0))
78    }
79}
80
81impl fmt::Debug for ReactiveCommandParameter {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        f.debug_tuple("ReactiveCommandParameter").field(&(Rc::as_ptr(&self.0) as *const ())).finish()
84    }
85}
86
87/// The value a command of ReactiveUI is executed with, from the value of a control's
88/// `CommandParameter`: the value itself, or the object of a [`ReactiveCommandParameter`].
89pub fn parameter_as_any(parameter: &BoxedValue) -> &dyn Any {
90    let value: &dyn Any = (**parameter).as_any();
91    match value.downcast_ref::<ReactiveCommandParameter>() {
92        Some(wrapped) => &*wrapped.0,
93        None => value,
94    }
95}
96
97/// A command parameter of ReactiveUI as the value of a control's `CommandParameter`: a
98/// [`BoxedValue`] (or a handle to an object of FerroUI) as it is; a string, a number, a character
99/// or a boolean boxed as itself, so that the control's parameter compares as the value; anything
100/// else in a [`ReactiveCommandParameter`].
101///
102/// Deviation (DEVIATIONS.md, ReactiveUI controls): upstream's parameter is the same `object` on
103/// both sides; the port's `Rc<dyn Any>` does not compare, so it is converted.
104pub fn boxed_parameter(parameter: &CommandParameter) -> Option<BoxedValue> {
105    let parameter = parameter.as_ref()?;
106    let value: &dyn Any = &**parameter;
107    if let Some(boxed) = value.downcast_ref::<BoxedValue>() {
108        return Some(boxed.clone());
109    }
110    if let Some(object) = value.downcast_ref::<ferroui_base::Ref<ferroui_base::FerroObject>>() {
111        return Some(Rc::new(object.clone()) as BoxedValue);
112    }
113    Some(box_primitive(value).unwrap_or_else(|| Rc::new(ReactiveCommandParameter(parameter.clone())) as BoxedValue))
114}
115
116/// A string, a number, a character or a boolean boxed as itself.
117fn box_primitive(value: &dyn Any) -> Option<BoxedValue> {
118    fn boxed<T: Clone + PartialEq + 'static>(value: &dyn Any) -> Option<BoxedValue> {
119        value.downcast_ref::<T>().map(|value| Rc::new(value.clone()) as Rc<dyn AnyValue>)
120    }
121    boxed::<String>(value)
122        .or_else(|| boxed::<&'static str>(value))
123        .or_else(|| boxed::<bool>(value))
124        .or_else(|| boxed::<char>(value))
125        .or_else(|| boxed::<i8>(value))
126        .or_else(|| boxed::<i16>(value))
127        .or_else(|| boxed::<i32>(value))
128        .or_else(|| boxed::<i64>(value))
129        .or_else(|| boxed::<isize>(value))
130        .or_else(|| boxed::<u8>(value))
131        .or_else(|| boxed::<u16>(value))
132        .or_else(|| boxed::<u32>(value))
133        .or_else(|| boxed::<u64>(value))
134        .or_else(|| boxed::<usize>(value))
135        .or_else(|| boxed::<f32>(value))
136        .or_else(|| boxed::<f64>(value))
137}