maomi/
prop.rs

1//! The properties utilities.
2//!
3//! The properties of components can be set through templates by component users.
4//!
5//! ### Basic Usage
6//!
7//! The following example show the basic usage of properties.
8//!
9//! ```rust
10//! use maomi::prelude::*;
11//!
12//! #[component]
13//! struct MyComponent {
14//!     template: template! {
15//!         /* ... */
16//!     },
17//!     // define a property with the detailed type
18//!     my_prop: Prop<usize>,
19//! }
20//!
21//! impl Component for MyComponent {
22//!     fn new() -> Self {
23//!         Self {
24//!             template: Default::default(),
25//!             // init the property with a default value
26//!             my_prop: Prop::new(123),
27//!         }
28//!     }
29//! }
30//!
31//! #[component]
32//! struct MyComponentUser {
33//!     template: template! {
34//!         // set the property value
35//!         <MyComponent my_prop=&{ 456 } />
36//!     },
37//! }
38//! ```
39//!
40//! ### Two-way Property
41//!
42//! Most property values are passing from the component user to the component.
43//! The component should not modify its own properties,
44//! otherwise the next updates of the component user will change them back.
45//! However, some properties (like `value` property in `<input>` ) should be passing back from the component to the component user.
46//! `BindingProp` is designed to solve this problem.
47//!
48//! A `BindingProp` accepts a `BindingValue` .
49//! A `BindingValue` contains a value shared between the component and the component user.
50//! It can be visited on both ends.
51//!
52//! ```rust
53//! use maomi::prelude::*;
54//! use maomi::prop::{BindingProp, BindingValue};
55//!
56//! #[component]
57//! struct MyComponent {
58//!     template: template! {
59//!         /* ... */
60//!     },
61//!     // define a two-way property with the detailed type
62//!     my_prop: BindingProp<String>,
63//! }
64//!
65//! impl Component for MyComponent {
66//!     fn new() -> Self {
67//!         Self {
68//!             template: Default::default(),
69//!             // init the two-way property
70//!             my_prop: BindingProp::new(String::new()),
71//!         }
72//!     }
73//! }
74//!
75//! #[component]
76//! struct MyComponentUser {
77//!     template: template! {
78//!         // associate a binding value
79//!         <MyComponent my_prop={ &self.comp_value } />
80//!     },
81//!     comp_value: BindingValue<String>,
82//! }
83//!
84//! impl Component for MyComponentUser {
85//!     fn new() -> Self {
86//!         Self {
87//!             template: Default::default(),
88//!             // init the binding value
89//!             comp_value: BindingValue::new(String::new()),
90//!         }
91//!     }
92//! }
93//! ```
94//!
95//! ### List Property
96//!
97//! `ListProp` is one special kind of properties.
98//! It can accepts one attribute more than once.
99//! This helps some special cases like `class:xxx` syntax in `maomi_dom` crate.
100//!
101//! ```rust
102//! use maomi::prelude::*;
103//! use maomi::prop::ListProp;
104//!
105//! #[component]
106//! struct MyComponent {
107//!     template: template! {
108//!         /* ... */
109//!     },
110//!     // define a list property with the detailed item type
111//!     my_prop: ListProp<String>,
112//! }
113//!
114//! impl Component for MyComponent {
115//!     fn new() -> Self {
116//!         Self {
117//!             template: Default::default(),
118//!             // init the list property
119//!             my_prop: ListProp::new(),
120//!         }
121//!     }
122//! }
123//!
124//! #[component]
125//! struct MyComponentUser {
126//!     template: template! {
127//!         // set the list property value
128//!         <MyComponent my_prop:String="abc" my_prop:String="def" />
129//!         // this is the same as following
130//!         <MyComponent my_prop={ &["abc".to_string(), "def".to_string()] } />
131//!     },
132//! }
133//! ```
134
135use std::{borrow::Borrow, cell::RefCell, fmt::Display, ops::Deref, rc::Rc};
136
137/// The property updater.
138///
139/// This trait is implemented by `Prop` .
140/// Custom property types that implements this trait can also be set through templates.
141pub trait PropertyUpdate<S: ?Sized> {
142    /// Must be `bool` if used in components and updated through templates.
143    type UpdateContext;
144
145    /// The updater.
146    ///
147    /// If used in components and updated through templates,
148    /// `ctx` must be set to true if updated.
149    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut Self::UpdateContext);
150}
151
152/// A property of components that can be set through templates.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
154pub struct Prop<T> {
155    inner: T,
156}
157
158impl<T> Prop<T> {
159    /// Create the property with initial value.
160    #[inline]
161    pub fn new(inner: T) -> Self {
162        Self { inner }
163    }
164}
165
166impl<T> Deref for Prop<T> {
167    type Target = T;
168
169    #[inline]
170    fn deref(&self) -> &Self::Target {
171        &self.inner
172    }
173}
174
175impl<T> AsRef<T> for Prop<T> {
176    #[inline]
177    fn as_ref(&self) -> &T {
178        &self.inner
179    }
180}
181
182impl<T> Borrow<T> for Prop<T> {
183    #[inline]
184    fn borrow(&self) -> &T {
185        &self.inner
186    }
187}
188
189impl<T: Display> Display for Prop<T> {
190    #[inline]
191    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192        self.inner.fmt(f)
193    }
194}
195
196/// Indicate that `&S` is assignable to `Prop<Self>` .
197///
198/// Every type that implements `PartialEq` and can be borrowed as `&S` automatically implements this trait.
199/// For example:
200/// * `usize` implements `PropAsRef<usize>` ;
201/// * `String` implements `PropAsRef<String>` and `PropAsRef<str>` .
202pub trait PropAsRef<S: ?Sized + PartialEq> {
203    /// Borrow `&Self` as `&S` .
204    fn property_as_ref(&self) -> &S;
205    /// Clone `&S` as a new `Self` .
206    fn property_to_owned(s: &S) -> Self
207    where
208        Self: Sized;
209}
210
211impl<S: ?Sized + PartialEq + ToOwned<Owned = T>, T: Borrow<S>> PropAsRef<S> for T {
212    #[inline]
213    fn property_as_ref(&self) -> &S {
214        self.borrow()
215    }
216
217    #[inline]
218    fn property_to_owned(s: &S) -> Self
219    where
220        Self: Sized,
221    {
222        s.to_owned()
223    }
224}
225
226impl<S: ?Sized + PartialEq, T: PropAsRef<S>> PropertyUpdate<S> for Prop<T> {
227    type UpdateContext = bool;
228
229    #[inline]
230    fn compare_and_set_ref(dest: &mut Self, src: &S, ctx: &mut bool) {
231        if dest.inner.property_as_ref() == src {
232            return;
233        }
234        dest.inner = PropAsRef::property_to_owned(src);
235        *ctx = true;
236    }
237}
238
239/// A two-way property that can share a `BindingValue` between a component and its user.
240#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
241pub struct BindingProp<T> {
242    value: BindingValue<T>,
243}
244
245impl<T> BindingProp<T> {
246    /// Create the property with initial value.
247    #[inline]
248    pub fn new(default_value: T) -> Self {
249        Self {
250            value: BindingValue::new(default_value),
251        }
252    }
253
254    /// Set the value.
255    #[inline]
256    pub fn set(&mut self, v: T) {
257        self.value.set(v);
258    }
259
260    /// Get a reference of the value.
261    #[inline]
262    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
263        self.value.with(f)
264    }
265
266    /// Get a reference of the value.
267    #[inline]
268    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
269        self.value.update(f)
270    }
271}
272
273impl<T: Clone> BindingProp<T> {
274    /// Get the cloned value.
275    pub fn get(&self) -> T {
276        self.value.get()
277    }
278}
279
280impl<T> PropertyUpdate<BindingValue<T>> for BindingProp<T> {
281    type UpdateContext = bool;
282
283    #[inline]
284    fn compare_and_set_ref(dest: &mut Self, src: &BindingValue<T>, ctx: &mut Self::UpdateContext) {
285        if BindingValue::ptr_eq(&dest.value, src) {
286            return;
287        }
288        dest.value = src.clone_ref();
289        *ctx = true;
290    }
291}
292
293/// A value that can be associated to a `BindingProp` .
294///
295/// Note that the `BindingValue` should be exclusively associated to one `BindingProp` .
296/// Panics if the value is associated to more than one `BindingProp` .
297#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
298pub struct BindingValue<T> {
299    inner: Rc<RefCell<T>>,
300}
301
302impl<T> BindingValue<T> {
303    /// Create the property with initial value.
304    #[inline]
305    pub fn new(default_value: T) -> Self {
306        Self {
307            inner: Rc::new(RefCell::new(default_value)),
308        }
309    }
310
311    #[doc(hidden)]
312    #[inline]
313    pub fn ptr_eq(a: &Self, b: &Self) -> bool {
314        Rc::ptr_eq(&a.inner, &b.inner)
315    }
316
317    #[doc(hidden)]
318    #[inline]
319    pub fn clone_ref(&self) -> Self {
320        if Rc::strong_count(&self.inner) > 1 {
321            panic!("A `BindingValue` cannot be associated to more than one `BindingProp`");
322        }
323        Self {
324            inner: self.inner.clone(),
325        }
326    }
327
328    /// Set the value.
329    ///
330    /// Updates of the value will NOT be applied to template!
331    /// To change the value and apply in templates, create a new `BindingValue` instead.
332    #[inline]
333    pub fn set(&mut self, v: T) {
334        *self.inner.borrow_mut() = v;
335    }
336
337    /// Get a reference of the value.
338    #[inline]
339    pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
340        f(&(*self.inner).borrow())
341    }
342
343    /// Get a reference of the value.
344    ///
345    /// Updates of the value will NOT be applied to template!
346    /// To change the value and apply in templates, create a new `BindingValue` instead.
347    #[inline]
348    pub fn update<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
349        f(&mut (*self.inner).borrow_mut())
350    }
351}
352
353impl<T: Clone> BindingValue<T> {
354    /// Get the cloned value.
355    pub fn get(&self) -> T {
356        (*self.inner).borrow().clone()
357    }
358}
359
360/// The list property initializer.
361pub trait ListPropertyInit {
362    /// Must be `bool` if used in components and updated through templates.
363    type UpdateContext;
364
365    /// Initialize with item count provided.
366    ///
367    /// Will be called once before any list value set.
368    fn init_list(dest: &mut Self, count: usize, ctx: &mut Self::UpdateContext)
369    where
370        Self: Sized;
371}
372
373/// The list property updater.
374///
375/// This trait is implemented by `ListProp` .
376/// Custom event types that implements this trait can also be used in templates with `:xxx=` syntax.
377pub trait ListPropertyUpdate<S: ?Sized>: ListPropertyInit {
378    /// The item value type.
379    ///
380    /// Must match the corresponding `ListPropertyItem::Value` .
381    type ItemValue: ?Sized;
382
383    /// The updater.
384    ///
385    /// If used in components and updated through templates,
386    /// `ctx` must be set to true if updated.
387    fn compare_and_set_item_ref<U: ListPropertyItem<Self, S, Value = Self::ItemValue>>(
388        dest: &mut Self,
389        index: usize,
390        src: &S,
391        ctx: &mut Self::UpdateContext,
392    ) where
393        Self: Sized;
394}
395
396/// The item updater for a specified list property `L` .
397pub trait ListPropertyItem<L: ListPropertyUpdate<S>, S: ?Sized> {
398    /// The item value type.
399    ///
400    /// Must match the corresponding `ListPropertyUpdate::ItemValue` .
401    type Value: ?Sized;
402
403    /// Get the item value.
404    ///
405    /// If used in components and updated through templates,
406    /// `ctx` must be set to true if updated.
407    fn item_value<'a>(
408        dest: &mut L,
409        index: usize,
410        s: &'a S,
411        ctx: &mut L::UpdateContext,
412    ) -> &'a Self::Value;
413}
414
415/// A list property that can be used in templates.
416///
417/// List properties can be updated in `:xxx=` syntax,
418/// while the `item_name` is a type that implements `ListPropertyItem` .
419// TODO add better examples and documentation for it
420#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
421pub struct ListProp<T: Default> {
422    inner: Box<[T]>,
423}
424
425impl<T: Default> ListProp<T> {
426    /// Create the property with no item.
427    #[inline]
428    pub fn new() -> Self {
429        Self {
430            inner: Box::new([]),
431        }
432    }
433}
434
435impl<'a, T: Default> IntoIterator for &'a ListProp<T> {
436    type IntoIter = std::slice::Iter<'a, T>;
437    type Item = &'a T;
438
439    #[inline]
440    fn into_iter(self) -> Self::IntoIter {
441        self.inner.into_iter()
442    }
443}
444
445impl<T: Default> Deref for ListProp<T> {
446    type Target = [T];
447
448    #[inline]
449    fn deref(&self) -> &Self::Target {
450        &self.inner
451    }
452}
453
454impl<T: Default> AsRef<[T]> for ListProp<T> {
455    #[inline]
456    fn as_ref(&self) -> &[T] {
457        &self.inner
458    }
459}
460
461impl<T: Default> Borrow<[T]> for ListProp<T> {
462    #[inline]
463    fn borrow(&self) -> &[T] {
464        &self.inner
465    }
466}
467
468impl<T: Default + PartialEq + Clone> PropertyUpdate<[T]> for ListProp<T> {
469    type UpdateContext = bool;
470
471    #[inline]
472    fn compare_and_set_ref(dest: &mut Self, src: &[T], ctx: &mut Self::UpdateContext) {
473        if &*dest.inner == src {
474            return;
475        }
476        dest.inner = src.iter().cloned().collect();
477        *ctx = true;
478    }
479}
480
481impl<T: Default> ListPropertyInit for ListProp<T> {
482    type UpdateContext = bool;
483
484    #[inline]
485    fn init_list(dest: &mut Self, count: usize, _ctx: &mut bool) {
486        let mut v = Vec::with_capacity(count);
487        v.resize_with(count, T::default);
488        dest.inner = v.into_boxed_slice();
489    }
490}
491
492impl<S: ?Sized + PartialEq, T: Default + PropAsRef<S>> ListPropertyUpdate<S> for ListProp<T> {
493    type ItemValue = ();
494
495    #[inline]
496    fn compare_and_set_item_ref<U: ListPropertyItem<Self, S, Value = ()>>(
497        dest: &mut Self,
498        index: usize,
499        src: &S,
500        ctx: &mut Self::UpdateContext,
501    ) where
502        Self: Sized,
503    {
504        U::item_value(dest, index, src, ctx);
505    }
506}
507
508impl<S: ?Sized + PartialEq, T: Default + PropAsRef<S>> ListPropertyItem<ListProp<T>, S> for T {
509    type Value = ();
510
511    #[inline]
512    fn item_value<'a>(
513        dest: &mut ListProp<T>,
514        index: usize,
515        src: &'a S,
516        ctx: &mut bool,
517    ) -> &'a Self::Value {
518        if dest.inner[index].property_as_ref() == src {
519            return &();
520        }
521        dest.inner[index] = PropAsRef::property_to_owned(src);
522        *ctx = true;
523        &()
524    }
525}