nami/
project.rs

1use crate::Binding;
2
3/// Trait for projecting bindings into their component parts.
4///
5/// This trait enables decomposing complex bindings (such as tuples or structs)
6/// into separate bindings for each field, allowing granular reactive updates
7/// to individual components without affecting the entire structure.
8///
9/// # Purpose
10///
11/// When working with complex data structures in reactive systems, it's often
12/// desirable to bind to individual fields rather than the entire structure.
13/// The `Project` trait provides this capability by creating "projected" bindings
14/// that maintain bidirectional reactivity with the original binding.
15///
16/// # Examples
17///
18/// ## Tuple projection
19/// ```rust
20/// use nami::{Binding, binding};
21/// use nami::project::Project;
22///
23/// // Create a binding to a tuple
24/// let tuple_binding: Binding<(i32, &'static str)> = binding((42, "hello"));
25///
26/// // Project it into separate bindings for each element
27/// let (mut num_binding, str_binding): (Binding<i32>, Binding<&'static str>) = tuple_binding.project();
28///
29/// // Changes to individual projections update the original
30/// num_binding.set(100);
31/// assert_eq!(tuple_binding.get().0, 100);
32/// ```
33///
34/// ## Struct projection with derive macro
35/// ```rust
36/// use nami::{Binding, binding};
37///
38/// #[cfg(feature = "derive")]
39/// # {
40/// #[derive(Clone, nami::Project)]
41/// struct Person {
42///     name: String,
43///     age: u32,
44/// }
45///
46/// let person_binding: Binding<Person> = binding(Person {
47///     name: "Alice".to_string(),
48///     age: 30,
49/// });
50///
51/// let mut projected: PersonProjected = person_binding.project();
52/// projected.name.set_from("Bob");
53/// projected.age.set(25);
54///
55/// let person = person_binding.get();
56/// assert_eq!(person.name, "Bob");
57/// assert_eq!(person.age, 25);
58/// # }
59/// ```
60pub trait Project: Sized {
61    /// The type resulting from projection.
62    ///
63    /// For tuples, this is typically a tuple of `Binding<T>` for each element.
64    /// For structs, this would be a struct containing `Binding<T>` for each field.
65    type Projected;
66
67    /// Creates projected bindings from a source binding.
68    ///
69    /// This method decomposes the source binding into separate bindings
70    /// for each component, maintaining bidirectional reactivity between
71    /// the projected bindings and the original source.
72    ///
73    /// # Parameters
74    ///
75    /// * `source` - The binding to project from
76    ///
77    /// # Returns
78    ///
79    /// A structure containing individual bindings for each component,
80    /// where changes to any projected binding will update the corresponding
81    /// field in the source binding.
82    fn project(source: &Binding<Self>) -> Self::Projected;
83}
84
85/// Internal macro for generating tuple implementations.
86///
87/// This macro generates implementations for tuples of various sizes,
88/// from 2-element tuples up to 14-element tuples.
89macro_rules! tuples {
90    ($macro:ident) => {
91        $macro!((T0, 0), (T1, 1));
92        $macro!((T0, 0), (T1, 1), (T2, 2));
93        $macro!((T0, 0), (T1, 1), (T2, 2), (T3, 3));
94        $macro!((T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4));
95        $macro!((T0, 0), (T1, 1), (T2, 2), (T3, 3), (T4, 4), (T5, 5));
96        $macro!(
97            (T0, 0),
98            (T1, 1),
99            (T2, 2),
100            (T3, 3),
101            (T4, 4),
102            (T5, 5),
103            (T6, 6)
104        );
105        $macro!(
106            (T0, 0),
107            (T1, 1),
108            (T2, 2),
109            (T3, 3),
110            (T4, 4),
111            (T5, 5),
112            (T6, 6),
113            (T7, 7)
114        );
115        $macro!(
116            (T0, 0),
117            (T1, 1),
118            (T2, 2),
119            (T3, 3),
120            (T4, 4),
121            (T5, 5),
122            (T6, 6),
123            (T7, 7),
124            (T8, 8)
125        );
126        $macro!(
127            (T0, 0),
128            (T1, 1),
129            (T2, 2),
130            (T3, 3),
131            (T4, 4),
132            (T5, 5),
133            (T6, 6),
134            (T7, 7),
135            (T8, 8),
136            (T9, 9)
137        );
138        $macro!(
139            (T0, 0),
140            (T1, 1),
141            (T2, 2),
142            (T3, 3),
143            (T4, 4),
144            (T5, 5),
145            (T6, 6),
146            (T7, 7),
147            (T8, 8),
148            (T9, 9),
149            (T10, 10)
150        );
151        $macro!(
152            (T0, 0),
153            (T1, 1),
154            (T2, 2),
155            (T3, 3),
156            (T4, 4),
157            (T5, 5),
158            (T6, 6),
159            (T7, 7),
160            (T8, 8),
161            (T9, 9),
162            (T10, 10),
163            (T11, 11)
164        );
165        $macro!(
166            (T0, 0),
167            (T1, 1),
168            (T2, 2),
169            (T3, 3),
170            (T4, 4),
171            (T5, 5),
172            (T6, 6),
173            (T7, 7),
174            (T8, 8),
175            (T9, 9),
176            (T10, 10),
177            (T11, 11),
178            (T12, 12)
179        );
180        $macro!(
181            (T0, 0),
182            (T1, 1),
183            (T2, 2),
184            (T3, 3),
185            (T4, 4),
186            (T5, 5),
187            (T6, 6),
188            (T7, 7),
189            (T8, 8),
190            (T9, 9),
191            (T10, 10),
192            (T11, 11),
193            (T12, 12),
194            (T13, 13)
195        );
196    };
197}
198
199/// Internal macro for implementing the `Project` trait for tuples.
200///
201/// This macro generates `Project` implementations for tuples by creating
202/// bidirectional bindings for each tuple element using `Binding::mapping`.
203/// Each projected binding maintains reactivity with its corresponding
204/// field in the original tuple.
205macro_rules! impl_project {
206    ( $(($ty:ident, $idx:tt)),+ ) => {
207        impl< $( $ty: 'static+Clone ),+ > Project for ( $( $ty ),+ ) {
208            type Projected = ( $( Binding<$ty> ),+ );
209
210            fn project(source: &Binding<Self>) -> Self::Projected {
211                (
212                    $(
213                        {
214                            let source = source.clone();
215                            Binding::mapping(
216                                &source,
217                                |value| value.$idx,
218                                move |binding, value| {
219                                    binding.with_mut(|b| {
220                                        b.$idx = value;
221                                    });
222                                },
223                            )
224                        }
225                    ),+
226                )
227            }
228        }
229    };
230    () => {};
231}
232
233// Generate Project implementations for all tuple sizes
234tuples!(impl_project);
235
236impl<T: Project> Binding<T> {
237    /// Projects this binding into its component parts.
238    ///
239    /// This method uses the `Project` trait implementation to decompose
240    /// the binding into separate reactive bindings for each component.
241    /// Changes to any projected binding will be reflected in the original
242    /// binding and vice versa.
243    ///
244    /// # Examples
245    ///
246    /// ```rust
247    /// use nami::{Binding, binding};
248    ///
249    /// let tuple_binding: Binding<(i32, i32, i32)> = binding((1, 2, 3));
250    /// let (mut a, mut b, c) = tuple_binding.project();
251    ///
252    /// // Modify individual projections
253    /// a.set(10);
254    /// b.set(20);
255    ///
256    /// // Original binding reflects changes
257    /// assert_eq!(tuple_binding.get(), (10, 20, 3));
258    /// ```
259    #[must_use]
260    pub fn project(&self) -> T::Projected {
261        T::project(self)
262    }
263}