Skip to main content

ferroui_reactiveui_controls/
reactive_user_control_base.rs

1//! `ReactiveUserControlBase` (`ReactiveUserControlBase.cs`): a non-generic ReactiveUI
2//! `UserControl` base that owns the view model property.
3
4use crate::reactive_view::{IReactiveViewClass, ITypedReactiveViewClass, ReactiveViewFor, ViewModelFilter};
5use crate::view_model_property_sync::ViewModelPropertySync;
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::LayoutableImpl;
9use ferroui_base::{
10    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, FerroObjectImpl,
11    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty, VisualImpl,
12};
13use ferroui_controls::primitives::TemplatedControlImpl;
14use ferroui_controls::{ContentControlImpl, ControlImpl, UserControl};
15use ferroui_reactiveui::IViewFor;
16use std::cell::Cell;
17use std::rc::Rc;
18
19/// A non-generic ReactiveUI `UserControl` base that owns the view model property
20/// (`ReactiveUserControlBase`).
21///
22/// When the control is activated, the view model it shows is activated with it, provided the view
23/// model implements `IActivatableViewModel`. The view model property and the data context follow
24/// each other ([`ViewModelPropertySync`]).
25///
26/// The typed form is [`ReactiveUserControl<VM>`](crate::ReactiveUserControl); markup creates this
27/// class.
28#[repr(C)]
29pub struct ReactiveUserControlBase {
30    base: UserControl,
31    /// The view models the control takes from its data context, when a typed handle restricted
32    /// them (`IsValidViewModelValue` of `ReactiveUserControl<TViewModel>`).
33    view_model_filter: Cell<Option<ViewModelFilter>>,
34}
35
36ferro_class!(ReactiveUserControlBase: UserControl);
37ferro_class_info!(ReactiveUserControlBase { new: ReactiveUserControlBase::new });
38ferro_impl_classes!(
39    ReactiveUserControlBase: StyledElementImpl,
40    VisualImpl,
41    LayoutableImpl,
42    InteractiveImpl,
43    InputElementImpl,
44    ControlImpl,
45    TemplatedControlImpl,
46    ContentControlImpl
47);
48
49impl FerroObjectImpl for ReactiveUserControlBase {
50    /// The constructor body: the view's activation is forwarded to its view model
51    /// (`ViewModelPropertySync.ForwardActivation`).
52    fn constructed(this: &Self) {
53        Self::parent_constructed(this);
54        ViewModelPropertySync::forward_activation(&this.to_ref());
55    }
56
57    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
58        Self::parent_on_property_changed(this, change);
59
60        ViewModelPropertySync::apply(this, change, Self::view_model_property(), |value| {
61            IReactiveViewClass::is_valid_view_model_value(this, value)
62        });
63    }
64}
65
66ferro_properties! {
67    impl ReactiveUserControlBase {
68        /// Identifies the `ViewModel` property (`ViewModelProperty`).
69        pub fn view_model_property() -> StyledProperty<Option<BoxedValue>> {
70            FerroProperty::register::<ReactiveUserControlBase, _>("ViewModel", None)
71        }
72    }
73}
74
75impl ReactiveUserControlBase {
76    /// Field initialisation.
77    pub fn construct() -> Self {
78        Self { base: UserControl::construct(), view_model_filter: Cell::new(None) }
79    }
80
81    /// Creates the control.
82    pub fn new() -> Ref<Self> {
83        instantiate(Self::construct())
84    }
85
86    /// The view model, as the untyped value of the property (`IViewFor.ViewModel { get; }`).
87    pub fn view_model_value(&self) -> Option<BoxedValue> {
88        self.get_value(Self::view_model_property())
89    }
90
91    /// Sets the view model, as the untyped value of the property (`IViewFor.ViewModel { set; }`).
92    pub fn set_view_model_value(&self, value: Option<BoxedValue>) {
93        self.set_value(Self::view_model_property(), value)
94    }
95
96    /// The control as a view of ReactiveUI (`(IViewFor)control`), which keeps it alive.
97    pub fn view_for(this: &Ref<Self>) -> Rc<dyn IViewFor> {
98        ReactiveViewFor::new(this.clone())
99    }
100}
101
102impl IReactiveViewClass for ReactiveUserControlBase {
103    fn view_model_property() -> &'static StyledProperty<Option<BoxedValue>> {
104        ReactiveUserControlBase::view_model_property()
105    }
106
107    /// `IsValidViewModelValue`: any value, unless a typed handle restricted the view models.
108    fn is_valid_view_model_value(&self, value: Option<&BoxedValue>) -> bool {
109        match (value, self.view_model_filter.get()) {
110            (Some(value), Some(filter)) => filter(value),
111            _ => true,
112        }
113    }
114}
115
116impl ITypedReactiveViewClass for ReactiveUserControlBase {
117    fn create() -> Ref<Self> {
118        Self::new()
119    }
120
121    fn set_view_model_filter(&self, filter: Option<ViewModelFilter>) {
122        self.view_model_filter.set(filter);
123    }
124}