Skip to main content

ferroui_view/
builder.rs

1//! The builders of elements: [`El`], the element of a control class, its
2//! methods, and the conversions of what a setter takes ([`IntoProp`]).
3//!
4//! Every class gets a function that starts its element (`button()`) and a
5//! trait with a setter per property and a handler per routed event
6//! (`ButtonProps`), generated from the sources (`generated/builders.rs`). The
7//! setters take what converts to the value of the property: the value, a
8//! value that converts to it (`"text"` for an optional string, `8.0` for a
9//! thickness), a [`Signal`] or a [`Memo`] (the property follows it), a
10//! closure (computed reactively), a two-way signal, a binding, or an element
11//! for a property that holds an object (`content`).
12//!
13//! What the upstream API writes with indexers and operators on properties is
14//! here a method of the element:
15//!
16//! | upstream C#                                   | builder                                      |
17//! |-----------------------------------------------|----------------------------------------------|
18//! | `control[Grid.RowProperty] = 1`               | `.set(Grid::row_property(), 1)` or `.grid_row(1)` |
19//! | `control[!TextBlock.TextProperty] = binding`  | `.bind(TextBlock::text_property(), binding)` |
20//! | `control.Bind(Prop, observable)`              | `.text(signal)`, `.text(move \|\| ..)`        |
21//! | `!!` / two-way binding                        | `.text(signal.two_way())`                    |
22//! | `control.Classes.Add("accent")`               | `.class("accent")`                           |
23//! | `control.Name = "x"`                          | `.name("x")`                                 |
24
25use crate::element::{Attach, ControlElement, Element, ElementClass, IntoChild, IntoChildren, IntoElement, PropEntry, PropValue};
26use crate::reactive::{Memo, Signal, TwoWay};
27use ferroui_base::data::BindingBase;
28use ferroui_base::interactivity::{IRoutedEventArgs, Interactive, RoutedEvent};
29use ferroui_base::{AttachedProperty, BoxedValue, DirectProperty, FerroObject, FerroProperty, ObjectType, PropertyValue, Ref, StyledProperty, Upcast};
30use ferroui_controls::Control;
31use std::any::Any;
32use std::cell::RefCell;
33use std::marker::PhantomData;
34use std::rc::Rc;
35
36/// A class whose elements can be built: it makes its control.
37pub trait ViewClass: ObjectType + Upcast<Control> {
38    /// Creates a control of the class.
39    fn create() -> Ref<Self>;
40}
41
42/// What an element name of `rsx!` resolves to: a control class (every
43/// [`ViewClass`]) or a component (`#[component]` declares a type of the name
44/// of its function). `Button { .. }` in `rsx!` is `<Button as Builder>::builder()`.
45pub trait Builder {
46    /// The builder of an element of the name.
47    type Output;
48    /// Starts an element.
49    fn builder() -> Self::Output;
50}
51
52impl<T: ViewClass> Builder for T {
53    type Output = El<T>;
54    fn builder() -> El<T> {
55        El::new()
56    }
57}
58
59/// A property of a type known to the compiler: what the setters of an
60/// element take.
61pub trait TypedProperty: 'static {
62    /// The type of the value of the property.
63    type Value: PropertyValue;
64    /// The property, untyped.
65    fn untyped(&'static self) -> &'static FerroProperty;
66}
67
68impl<T: PropertyValue> TypedProperty for StyledProperty<T> {
69    type Value = T;
70    fn untyped(&'static self) -> &'static FerroProperty {
71        self.as_property()
72    }
73}
74
75impl<T: PropertyValue> TypedProperty for AttachedProperty<T> {
76    type Value = T;
77    fn untyped(&'static self) -> &'static FerroProperty {
78        let styled: &'static StyledProperty<T> = self;
79        styled.as_property()
80    }
81}
82
83impl<O: ObjectType, T: PropertyValue> TypedProperty for DirectProperty<O, T> {
84    type Value = T;
85    fn untyped(&'static self) -> &'static FerroProperty {
86        let base: &'static ferroui_base::DirectPropertyBase<T> = self;
87        base.as_property()
88    }
89}
90
91/// A conversion of what a setter is given to the value of a property:
92/// the value itself, and the conversions that make builders short (text to
93/// an optional string, a number to a thickness, a color to a brush).
94pub trait IntoValue<T> {
95    /// The value of the property.
96    fn into_value(self) -> T;
97}
98
99impl<T> IntoValue<T> for T {
100    fn into_value(self) -> T {
101        self
102    }
103}
104
105/// A conversion of a value of a property back to the type of a signal, for
106/// a two-way value: the reverse of [`IntoValue`].
107pub trait FromValue<T>: Sized {
108    /// The value of the signal, or `None` when the value of the property has
109    /// none (the signal keeps its value).
110    fn from_value(value: &T) -> Option<Self>;
111}
112
113impl<T: Clone> FromValue<T> for T {
114    fn from_value(value: &T) -> Option<Self> {
115        Some(value.clone())
116    }
117}
118
119/// The markers that tell the conversions of [`IntoProp`] apart.
120pub mod markers {
121    /// A value, converted.
122    pub struct Value;
123    /// A signal.
124    pub struct FromSignal<V>(std::marker::PhantomData<V>);
125    /// A memo.
126    pub struct FromMemo<V>(std::marker::PhantomData<V>);
127    /// A reactive closure.
128    pub struct FromClosure<V>(std::marker::PhantomData<V>);
129    /// A two-way signal.
130    pub struct FromTwoWay<V>(std::marker::PhantomData<V>);
131    /// An element held by an object property.
132    pub struct FromElement;
133    /// A binding.
134    pub struct FromBinding;
135}
136
137/// What a setter of a property whose value is `T` takes. `M` tells the
138/// conversions apart and is inferred.
139pub trait IntoProp<T, M> {
140    /// The value of the element.
141    fn into_prop(self) -> PropValue;
142}
143
144fn boxed<T: PropertyValue>(value: T) -> BoxedValue {
145    Rc::new(value)
146}
147
148impl<T: PropertyValue, V: IntoValue<T>> IntoProp<T, markers::Value> for V {
149    fn into_prop(self) -> PropValue {
150        PropValue::Value(boxed(self.into_value()))
151    }
152}
153
154impl<T: PropertyValue, V: IntoValue<T> + Clone + 'static> IntoProp<T, markers::FromSignal<V>> for Signal<V> {
155    fn into_prop(self) -> PropValue {
156        PropValue::Dynamic(Rc::new(move || boxed(self.get().into_value())))
157    }
158}
159
160impl<T: PropertyValue, V: IntoValue<T> + Clone + 'static> IntoProp<T, markers::FromMemo<V>> for Memo<V> {
161    fn into_prop(self) -> PropValue {
162        PropValue::Dynamic(Rc::new(move || boxed(self.get().into_value())))
163    }
164}
165
166impl<T: PropertyValue, V: IntoValue<T>, F: Fn() -> V + 'static> IntoProp<T, markers::FromClosure<V>> for F {
167    fn into_prop(self) -> PropValue {
168        PropValue::Dynamic(Rc::new(move || boxed(self().into_value())))
169    }
170}
171
172impl<T: PropertyValue, V: IntoValue<T> + FromValue<T> + Clone + PartialEq + 'static> IntoProp<T, markers::FromTwoWay<V>>
173    for TwoWay<V>
174{
175    fn into_prop(self) -> PropValue {
176        let signal = self.0;
177        PropValue::TwoWay {
178            get: Rc::new(move || boxed(signal.get().into_value())),
179            set: Rc::new(move |value: &dyn Any| {
180                if let Some(value) = value.downcast_ref::<T>().and_then(V::from_value) {
181                    if signal.is_alive() {
182                        signal.set_if_changed(value);
183                    }
184                }
185            }),
186        }
187    }
188}
189
190impl<E: IntoElement> IntoProp<Option<BoxedValue>, markers::FromElement> for E {
191    fn into_prop(self) -> PropValue {
192        PropValue::Element(Rc::new(RefCell::new(Some(self.into_element()))))
193    }
194}
195
196/// A binding of the property system given to a setter
197/// (`.text(Bind(binding))`).
198pub struct Bind<B: BindingBase + 'static>(pub B);
199
200impl<T: PropertyValue, B: BindingBase + 'static> IntoProp<T, markers::FromBinding> for Bind<B> {
201    fn into_prop(self) -> PropValue {
202        PropValue::Binding(Rc::new(self.0))
203    }
204}
205
206/// The element of a control class `T`, built with chained setters. Turns
207/// into an [`Element`].
208pub struct El<T> {
209    element: ControlElement,
210    _marker: PhantomData<fn() -> T>,
211}
212
213impl<T: ViewClass> El<T> {
214    /// An element of `T` with nothing set.
215    pub fn new() -> Self {
216        fn create<T: ViewClass>() -> Ref<Control> {
217            T::create().upcast()
218        }
219        let class = ElementClass {
220            name: std::any::type_name::<T>(),
221            type_id: std::any::TypeId::of::<T>,
222            create: create::<T>,
223        };
224        Self { element: ControlElement::new(class), _marker: PhantomData }
225    }
226}
227
228impl<T: ViewClass> Default for El<T> {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234/// The part of a builder the generated setters use.
235pub trait ElementBuilder: Sized {
236    /// The element being built.
237    fn element_mut(&mut self) -> &mut ControlElement;
238
239    /// Gives a property a value (the indexer `control[Property] = value`
240    /// upstream).
241    fn prop<P: TypedProperty + ?Sized, M>(mut self, property: &'static P, value: impl IntoProp<P::Value, M>) -> Self {
242        let property = property.untyped();
243        let value = value.into_prop();
244        let props = &mut self.element_mut().props;
245        props.retain(|entry| !std::ptr::eq(entry.property, property));
246        props.push(PropEntry { property, value });
247        self
248    }
249
250    /// Handles a routed event; the handler gets the control that handles it
251    /// and the arguments.
252    fn routed<A: IRoutedEventArgs + 'static>(
253        mut self,
254        event: &'static RoutedEvent<A>,
255        handler: impl Fn(&Interactive, &A) + 'static,
256    ) -> Self {
257        let handler = Rc::new(handler);
258        let attach: Attach = Rc::new(move |control: &Ref<Control>| {
259            let handler = handler.clone();
260            control.add_handler(event, move |sender, e| handler(sender, e));
261        });
262        self.element_mut().handlers.push(attach);
263        self
264    }
265}
266
267impl<T> ElementBuilder for El<T> {
268    fn element_mut(&mut self) -> &mut ControlElement {
269        &mut self.element
270    }
271}
272
273impl<T> El<T> {
274    /// The key of the element among its siblings.
275    pub fn key(mut self, key: impl Into<crate::Key>) -> Self {
276        self.element.key = Some(key.into());
277        self
278    }
279
280    /// The `Name` of the control.
281    pub fn name(mut self, name: impl Into<String>) -> Self {
282        self.element.name = Some(name.into());
283        self
284    }
285
286    /// Adds style classes, separated by spaces (`"accent large"`).
287    pub fn class(mut self, classes: &str) -> Self {
288        self.element.classes.extend(classes.split_whitespace().map(str::to_string));
289        self
290    }
291
292    /// Adds style classes, separated by spaces; the same as
293    /// [`class`](Self::class).
294    pub fn classes(self, classes: &str) -> Self {
295        self.class(classes)
296    }
297
298    /// Gives a property a value; the same as the generated setter of the
299    /// property, for any property (an attached one of another crate, one the
300    /// generator left out).
301    pub fn set<P: TypedProperty + ?Sized, M>(self, property: &'static P, value: impl IntoProp<P::Value, M>) -> Self {
302        self.prop(property, value)
303    }
304
305    /// Binds a property with a binding of the property system (the indexer
306    /// `control[!Property] = binding` upstream).
307    pub fn bind<P: TypedProperty + ?Sized>(mut self, property: &'static P, binding: impl BindingBase + 'static) -> Self {
308        let property = property.untyped();
309        self.element.props.push(PropEntry { property, value: PropValue::Binding(Rc::new(binding)) });
310        self
311    }
312
313    /// Handles a routed event with the control that handles it.
314    pub fn on<A: IRoutedEventArgs + 'static>(
315        self,
316        event: &'static RoutedEvent<A>,
317        handler: impl Fn(&Interactive, &A) + 'static,
318    ) -> Self {
319        self.routed(event, handler)
320    }
321
322    /// Calls `handler` with the new value whenever `property` changes on the
323    /// control (the user typed, a slider moved, a binding set it).
324    pub fn on_changed<P: TypedProperty + ?Sized>(mut self, property: &'static P, handler: impl Fn(&P::Value) + 'static) -> Self {
325        let untyped = property.untyped();
326        let handler = Rc::new(handler);
327        let attach: Attach = Rc::new(move |control: &Ref<Control>| {
328            let handler = handler.clone();
329            let subscription = control.property_changed(move |e| {
330                if std::ptr::eq(e.property(), untyped) {
331                    if let Some(value) = e.new_value().downcast_ref::<P::Value>() {
332                        handler(value);
333                    }
334                }
335            });
336            crate::reactive::on_cleanup(move || subscription.dispose());
337        });
338        self.element.handlers.push(attach);
339        self
340    }
341
342    /// Calls `f` with the control once it was made and given its values and
343    /// children: what a builder cannot say (a control kept in a
344    /// [`NodeRef`](crate::NodeRef), a call the class has no property for).
345    pub fn with(mut self, f: impl Fn(&Ref<T>) + 'static) -> Self
346    where
347        T: ObjectType,
348    {
349        let attach: Attach = Rc::new(move |control: &Ref<Control>| {
350            if let Some(control) = control.cast::<T>() {
351                f(&control);
352            }
353        });
354        self.element.on_mount.push(attach);
355        self
356    }
357
358    /// Keeps the control in `node_ref` once it is made.
359    pub fn node_ref(self, node_ref: crate::NodeRef<T>) -> Self
360    where
361        T: ObjectType,
362    {
363        self.with(move |control| node_ref.set(control.clone()))
364    }
365
366    /// Adds a child: an element, a builder, text or a list. Children go into
367    /// the content of the control: `Children` of a panel, `Items` of an items
368    /// control, `Content` of a content control, `Child` of a decorator, the
369    /// text of a `TextBlock`.
370    pub fn child(mut self, child: impl IntoChild) -> Self {
371        self.element.children.push(child.into_child());
372        self
373    }
374
375    /// Adds children: a vector, an array or a tuple of them.
376    pub fn children(mut self, children: impl IntoChildren) -> Self {
377        self.element.children.extend(children.into_children());
378        self
379    }
380
381    /// The element.
382    pub fn build(self) -> Element {
383        Element::Control(Box::new(self.element))
384    }
385}
386
387impl<T> IntoElement for El<T> {
388    fn into_element(self) -> Element {
389        self.build()
390    }
391}
392
393impl<T> From<El<T>> for Element {
394    fn from(builder: El<T>) -> Self {
395        builder.build()
396    }
397}
398
399/// The object a property is set on, for the generated code.
400#[doc(hidden)]
401pub fn object_of(control: &Ref<Control>) -> &FerroObject {
402    control
403}