Skip to main content

sequoia_view/
value.rs

1use sequoia_reactive::{Memo, Signal};
2
3use crate::live::{AnimSpec, Live, SpringSpec};
4
5pub type Value<T> = Live<T>;
6
7pub fn value<T: Clone + PartialEq + 'static>(v: T) -> Value<T> {
8    Live::fixed(v)
9}
10
11pub fn derived<T: Clone + PartialEq + 'static>(f: impl Fn() -> T + 'static) -> Value<T> {
12    Live::derived(f)
13}
14
15pub trait ValueExt<T: Clone + PartialEq + 'static>: Clone {
16    fn value_untracked(&self) -> T;
17
18    fn map_value<U: Clone + PartialEq + 'static>(&self, f: impl Fn(&T) -> U + 'static) -> Value<U>;
19
20    fn animated(self, spec: impl Into<AnimSpec>) -> Value<T>;
21
22    fn springed(self, spec: SpringSpec) -> Value<T>;
23}
24
25impl<T: Clone + PartialEq + 'static> ValueExt<T> for Signal<T> {
26    fn value_untracked(&self) -> T {
27        self.get_untracked()
28    }
29    fn map_value<U: Clone + PartialEq + 'static>(&self, f: impl Fn(&T) -> U + 'static) -> Value<U> {
30        let s = self.clone();
31        Live::derived(move || f(&s.get()))
32    }
33    fn animated(self, spec: impl Into<AnimSpec>) -> Value<T> {
34        Live::of_signal(self).animate(spec)
35    }
36    fn springed(self, spec: SpringSpec) -> Value<T> {
37        Live::of_signal(self).spring(spec)
38    }
39}
40
41impl<T: Clone + PartialEq + 'static> ValueExt<T> for Memo<T> {
42    fn value_untracked(&self) -> T {
43        self.get_untracked()
44    }
45    fn map_value<U: Clone + PartialEq + 'static>(&self, f: impl Fn(&T) -> U + 'static) -> Value<U> {
46        let m = self.clone();
47        Live::derived(move || f(&m.get()))
48    }
49    fn animated(self, spec: impl Into<AnimSpec>) -> Value<T> {
50        Live::of_memo(self).animate(spec)
51    }
52    fn springed(self, spec: SpringSpec) -> Value<T> {
53        Live::of_memo(self).spring(spec)
54    }
55}
56
57pub fn spring_spec(s: sequoia_animation::Spring) -> SpringSpec {
58    let w0 = std::f32::consts::TAU * s.frequency.max(0.0001);
59    let k = w0 * w0;
60    let c = 2.0 * s.damping_ratio.max(0.0) * w0;
61    SpringSpec {
62        stiffness: k,
63        damping: c,
64        mass: 1.0,
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn value_accepts_static_reactive_and_animated() {
74        fn takes_f32(_: impl Into<Value<f32>>) {}
75        let s = Signal::new(0.5f32);
76        takes_f32(0.8);
77        takes_f32(s.clone());
78        takes_f32(s.clone().animated(AnimSpec::SNAPPY));
79        takes_f32(s.clone().springed(SpringSpec::BOUNCY));
80        takes_f32(s.map_value(|v| v * 2.0));
81        takes_f32(value(0.1));
82        takes_f32(derived(|| 0.2));
83    }
84
85    #[test]
86    fn presets_are_sane() {
87        const { assert!(AnimSpec::FAST.duration_secs <= AnimSpec::SMOOTH.duration_secs) };
88        const { assert!(AnimSpec::SMOOTH.duration_secs <= AnimSpec::SLOW.duration_secs) };
89        const { assert!(AnimSpec::SNAPPY.duration_secs <= AnimSpec::GENTLE.duration_secs) };
90        let _ = (
91            AnimSpec::fast(),
92            AnimSpec::smooth(),
93            AnimSpec::snappy(),
94            AnimSpec::gentle(),
95        );
96        let _ = (
97            SpringSpec::snappy(),
98            SpringSpec::bouncy(),
99            SpringSpec::gentle(),
100        );
101    }
102
103    #[test]
104    fn spring_converts_without_nan() {
105        for s in [
106            sequoia_animation::Spring::snappy(),
107            sequoia_animation::Spring::bouncy(),
108            sequoia_animation::Spring::gentle(),
109        ] {
110            let spec = spring_spec(s);
111            assert!(spec.stiffness.is_finite() && spec.stiffness > 0.0);
112            assert!(spec.damping.is_finite() && spec.damping >= 0.0);
113        }
114    }
115
116    #[test]
117    fn value_ext_maps_and_animates() {
118        let s = Signal::new(false);
119        let v = s.map_value(|x| if *x { 1.0f32 } else { 0.6 });
120        assert_eq!(v.target_untracked(), 0.6);
121        s.set(true);
122        assert_eq!(v.target_untracked(), 1.0);
123        assert!(s.value_untracked());
124        let a = s.clone().animated(AnimSpec::SMOOTH);
125        assert!(a.is_animated());
126    }
127}