Skip to main content

ferroui_reactiveui_controls/
reactive_view.rs

1//! The reactive views as views of ReactiveUI: the untyped view of a control with a `ViewModel`
2//! property ([`ReactiveViewFor`], upstream's `IViewFor` on `ReactiveUserControlBase`,
3//! `ReactiveWindowBase` and `ViewModelViewHost`) and the typed reactive view ([`ReactiveView`],
4//! upstream's generic `ReactiveUserControl<TViewModel>` and `ReactiveWindow<TViewModel>`).
5//!
6//! Not an upstream file.
7//!
8//! Deviation (DEVIATIONS.md, ReactiveUI controls): a class of the object model is not generic
9//! (`ferro_class!` gives each class one type descriptor), so upstream's generic controls are a
10//! non-generic class (`ReactiveUserControlBase`, `ReactiveWindowBase`, which markup creates) and a
11//! typed handle over an instance of it, [`ReactiveView`], aliased as
12//! [`ReactiveUserControl<VM>`](crate::ReactiveUserControl) and
13//! [`ReactiveWindow<VM>`](crate::ReactiveWindow). The handle restricts the view models the control
14//! takes from its `DataContext` to `VM` (upstream's override of `IsValidViewModelValue`) by setting
15//! a filter on the instance.
16
17use crate::view_control::{register_view_control, IViewControl, ViewLink};
18use crate::view_model_value::{is_view_model_of, register_view_model, value_of_optional_view_model, view_model_of_value};
19use ferroui_base::{BoxedValue, IntoRef, ObjectType, Ref, StyledProperty, Upcast};
20use ferroui_controls::Control;
21use ferroui_reactiveui::{typed_view_for, IActivatableView, IViewFor, IViewForOf};
22use std::any::Any;
23use std::marker::PhantomData;
24use std::ops::Deref;
25use std::rc::Rc;
26
27/// The filter of the view models a reactive view takes from its `DataContext`
28/// (`IsValidViewModelValue`).
29pub type ViewModelFilter = fn(&BoxedValue) -> bool;
30
31/// A control class with a `ViewModel` property of the untyped value of the property system.
32pub trait IReactiveViewClass: ObjectType + Upcast<Control> {
33    /// The `ViewModel` property of the class (`ViewModelProperty`).
34    fn view_model_property() -> &'static StyledProperty<Option<BoxedValue>>;
35
36    /// Whether `value` may become the view model of the control (`IsValidViewModelValue`).
37    fn is_valid_view_model_value(&self, value: Option<&BoxedValue>) -> bool {
38        let _ = value;
39        true
40    }
41}
42
43/// A reactive view class that a typed handle can restrict to one type of view model.
44pub trait ITypedReactiveViewClass: IReactiveViewClass {
45    /// A new instance of the class (`new ReactiveUserControl<TViewModel>()`).
46    fn create() -> Ref<Self>;
47
48    /// Restricts the view models of the instance to those `filter` accepts; `None` accepts any.
49    fn set_view_model_filter(&self, filter: Option<ViewModelFilter>);
50}
51
52/// A control with a `ViewModel` property as an untyped view of ReactiveUI (`IViewFor`).
53pub struct ReactiveViewFor<C: IReactiveViewClass> {
54    view: ViewLink<C>,
55}
56
57impl<C: IReactiveViewClass> ReactiveViewFor<C> {
58    /// The view of `control`, which it keeps alive.
59    pub fn new(control: Ref<C>) -> Rc<Self> {
60        Self::with_link(ViewLink::Strong(control))
61    }
62
63    /// The view of `control`, which it does not keep alive (the view a control activates itself
64    /// with).
65    pub fn weak(control: &Ref<C>) -> Rc<Self> {
66        Self::with_link(ViewLink::Weak(control.downgrade()))
67    }
68
69    fn with_link(view: ViewLink<C>) -> Rc<Self> {
70        register_view_control::<Self>();
71        Rc::new(Self { view })
72    }
73
74    /// The control, while it is alive.
75    pub fn control(&self) -> Option<Ref<C>> {
76        self.view.get()
77    }
78
79    /// The view as `IViewFor`.
80    pub fn into_view_for(self: Rc<Self>) -> Rc<dyn IViewFor> {
81        self
82    }
83}
84
85impl<C: IReactiveViewClass> IViewControl for ReactiveViewFor<C> {
86    fn view_control(&self) -> Option<Ref<Control>> {
87        self.view.get().map(|control| control.upcast())
88    }
89}
90
91impl<C: IReactiveViewClass> IActivatableView for ReactiveViewFor<C> {
92    fn as_view_for(&self) -> Option<&dyn IViewFor> {
93        Some(self)
94    }
95}
96
97impl<C: IReactiveViewClass> IViewFor for ReactiveViewFor<C> {
98    fn view_model(&self) -> Option<Rc<dyn Any>> {
99        let control = self.view.get()?;
100        as_control(&*control).get_value(C::view_model_property()).as_ref().map(view_model_of_value)
101    }
102
103    fn set_view_model(&self, view_model: Option<Rc<dyn Any>>) {
104        if let Some(control) = self.view.get() {
105            as_control(&*control).set_value(C::view_model_property(), value_of_optional_view_model(view_model));
106        }
107    }
108}
109
110/// The control of an instance of a control class (the class as its base `Control`).
111pub(crate) fn as_control<C: Upcast<Control>>(control: &C) -> &Control {
112    Upcast::<Control>::upcast(control)
113}
114
115/// The filter of a typed view: a null view model, or one of type `VM` (`value is null or
116/// TViewModel`).
117fn accepts_view_model_of<VM: 'static>(value: &BoxedValue) -> bool {
118    is_view_model_of::<VM>(value)
119}
120
121/// A reactive view class instance typed by its view model (`ReactiveUserControl<TViewModel>`,
122/// `ReactiveWindow<TViewModel>`): a handle that dereferences to the control and, shared as an
123/// `Rc`, is its `IViewFor<TViewModel>`.
124pub struct ReactiveView<C: ITypedReactiveViewClass, VM: PartialEq + 'static> {
125    control: Ref<C>,
126    _view_model: PhantomData<fn() -> VM>,
127}
128
129impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> Clone for ReactiveView<C, VM> {
130    fn clone(&self) -> Self {
131        Self { control: self.control.clone(), _view_model: PhantomData }
132    }
133}
134
135impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> ReactiveView<C, VM> {
136    /// A new control of the class, typed by `VM` (`new ReactiveUserControl<TViewModel>()`).
137    pub fn new() -> Self {
138        Self::from_ref(C::create())
139    }
140
141    /// `control` (an instance of the class or of a class derived from it, as one of markup), typed
142    /// by `VM`: from now on it takes only view models of type `VM` from its `DataContext`.
143    pub fn from_control(control: impl IntoRef<C>) -> Self {
144        Self::from_ref(control.into_ref())
145    }
146
147    fn from_ref(control: Ref<C>) -> Self {
148        register_view_control::<Self>();
149        register_view_model::<VM>();
150        control.set_view_model_filter(Some(accepts_view_model_of::<VM>));
151        Self { control, _view_model: PhantomData }
152    }
153
154    /// The control.
155    pub fn control(&self) -> &Ref<C> {
156        &self.control
157    }
158
159    /// The view model (`ViewModel { get; }`): `None` when the control holds none, or holds a view
160    /// model of another type (which only the untyped `IViewFor` can set).
161    pub fn view_model(&self) -> Option<Rc<VM>> {
162        let value = as_control(&*self.control).get_value(C::view_model_property())?;
163        view_model_of_value(&value).downcast::<VM>().ok()
164    }
165
166    /// Sets the view model (`ViewModel { set; }`).
167    pub fn set_view_model(&self, view_model: Option<Rc<VM>>) {
168        as_control(&*self.control).set_value(C::view_model_property(), view_model.map(|view_model| view_model as BoxedValue));
169    }
170
171    /// The view as `IViewFor<TViewModel>`.
172    pub fn into_view_for_of(self) -> Rc<dyn IViewForOf<VM>> {
173        Rc::new(self)
174    }
175
176    /// The view as `IViewFor`.
177    pub fn into_view_for(self) -> Rc<dyn IViewFor> {
178        Rc::new(self)
179    }
180}
181
182impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> Default for ReactiveView<C, VM> {
183    fn default() -> Self {
184        Self::new()
185    }
186}
187
188impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> Deref for ReactiveView<C, VM> {
189    type Target = C;
190
191    fn deref(&self) -> &C {
192        &self.control
193    }
194}
195
196impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> IViewControl for ReactiveView<C, VM> {
197    fn view_control(&self) -> Option<Ref<Control>> {
198        Some(self.control.clone().upcast())
199    }
200}
201
202impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> IActivatableView for ReactiveView<C, VM> {
203    fn as_view_for(&self) -> Option<&dyn IViewFor> {
204        Some(self)
205    }
206}
207
208impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> IViewFor for ReactiveView<C, VM> {
209    fn view_model(&self) -> Option<Rc<dyn Any>> {
210        ReactiveView::view_model(self).map(|view_model| view_model as Rc<dyn Any>)
211    }
212
213    fn set_view_model(&self, view_model: Option<Rc<dyn Any>>) {
214        let typed = view_model.map(|view_model| match view_model.downcast::<VM>() {
215            Ok(view_model) => view_model,
216            Err(_) => panic!(
217                "InvalidCastException: the view model of {} is a {}",
218                std::any::type_name::<C>(),
219                std::any::type_name::<VM>()
220            ),
221        });
222        ReactiveView::set_view_model(self, typed);
223    }
224
225    fn as_view_for_of(self: Rc<Self>) -> Option<Box<dyn Any>> {
226        typed_view_for::<VM, Self>(self)
227    }
228}
229
230impl<C: ITypedReactiveViewClass, VM: PartialEq + 'static> IViewForOf<VM> for ReactiveView<C, VM> {
231    fn view_model(&self) -> Option<Rc<VM>> {
232        ReactiveView::view_model(self)
233    }
234
235    fn set_view_model(&self, view_model: Option<Rc<VM>>) {
236        ReactiveView::set_view_model(self, view_model);
237    }
238}