Skip to main content

ravenrs_reactive/
binding.rs

1use std::fmt;
2use std::rc::Rc;
3
4/// A read/write handle to a value owned elsewhere (SwiftUI's `@Binding`).
5///
6/// Components that edit a value (a toggle, a text field) take a `Binding` so the
7/// caller decides where the value lives. Reads are tracked like the underlying
8/// state's reads.
9pub struct Binding<T> {
10    get: Rc<dyn Fn() -> T>,
11    set: Rc<dyn Fn(T)>,
12}
13
14impl<T: 'static> Binding<T> {
15    /// Creates a binding from a getter and a setter.
16    pub fn new(get: impl Fn() -> T + 'static, set: impl Fn(T) + 'static) -> Self {
17        Self { get: Rc::new(get), set: Rc::new(set) }
18    }
19
20    /// A binding that always reads `value` and ignores writes; useful in previews and tests.
21    pub fn constant(value: T) -> Self
22    where
23        T: Clone,
24    {
25        Self::new(move || value.clone(), |_| {})
26    }
27
28    /// Reads the current value through the getter.
29    pub fn get(&self) -> T {
30        (self.get)()
31    }
32
33    /// Writes `value` through the setter.
34    pub fn set(&self, value: T) {
35        (self.set)(value)
36    }
37
38    /// Reads the value, lets `f` modify it, and writes it back.
39    pub fn update(&self, f: impl FnOnce(&mut T)) {
40        let mut v = self.get();
41        f(&mut v);
42        self.set(v);
43    }
44
45    /// Projects a binding onto a part of the value, e.g. one field of a struct.
46    pub fn map<U: 'static>(
47        &self,
48        get: impl Fn(&T) -> U + 'static,
49        set: impl Fn(&mut T, U) + 'static,
50    ) -> Binding<U> {
51        let (outer_get, outer_set) = (self.get.clone(), self.set.clone());
52        let read = self.get.clone();
53        Binding::new(
54            move || get(&read()),
55            move |u| {
56                let mut whole = outer_get();
57                set(&mut whole, u);
58                outer_set(whole);
59            },
60        )
61    }
62}
63
64impl<T> Clone for Binding<T> {
65    fn clone(&self) -> Self {
66        Self { get: self.get.clone(), set: self.set.clone() }
67    }
68}
69
70impl<T> fmt::Debug for Binding<T> {
71    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72        f.write_str("Binding")
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use crate::State;
79
80    #[derive(Clone, Debug, PartialEq)]
81    struct Settings {
82        volume: u8,
83        muted: bool,
84    }
85
86    #[test]
87    fn binding_writes_through_to_state() {
88        let s = State::new(1);
89        let b = s.binding();
90        b.update(|v| *v += 41);
91        assert_eq!(s.get(), 42);
92    }
93
94    #[test]
95    fn mapped_binding_edits_one_field() {
96        let s = State::new(Settings { volume: 3, muted: false });
97        let muted = s.binding().map(|s| s.muted, |s, m| s.muted = m);
98        muted.set(true);
99        assert_eq!(s.get(), Settings { volume: 3, muted: true });
100    }
101}