1use core::fmt;
4
5use crate::class::{Class, Eps, MotionPrefs};
6use crate::curve::Curve;
7use crate::lanes::{Animatable, Lanes, MAX_LANES, lane_count};
8
9pub const DEFAULT_PIXELS_PER_POINT: f32 = 2.0;
12
13pub struct Animated<T: Animatable> {
20 from: Lanes,
22 vel: Lanes,
24 target: Lanes,
26 curve: Curve,
27 class: Class,
28 eps: Eps,
29 t0: f64,
31 settle_at: f64,
33 rest: T,
35}
36
37impl<T: Animatable> Clone for Animated<T> {
38 fn clone(&self) -> Self {
39 *self
40 }
41}
42
43impl<T: Animatable> Copy for Animated<T> {}
44
45impl<T: Animatable> fmt::Debug for Animated<T> {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 let n = lane_count::<T>();
48 f.debug_struct("Animated")
49 .field("from", &self.from.get(..n))
50 .field("vel", &self.vel.get(..n))
51 .field("target", &self.target.get(..n))
52 .field("curve", &self.curve)
53 .field("class", &self.class)
54 .field("t0", &self.t0)
55 .field("settle_at", &self.settle_at)
56 .finish()
57 }
58}
59
60impl<T: Animatable> Animated<T> {
61 pub fn new(value: T, class: Class) -> Self {
63 let lanes = sane_lanes::<T>(value.to_lanes(), &[0.0; MAX_LANES]);
64 let rest = exact(value, lanes);
65 Animated {
66 from: lanes,
67 vel: [0.0; MAX_LANES],
68 target: lanes,
69 curve: Curve::Instant,
70 class,
71 eps: class.eps(DEFAULT_PIXELS_PER_POINT),
72 t0: 0.0,
73 settle_at: f64::NEG_INFINITY,
74 rest,
75 }
76 }
77
78 pub fn set_pixels_per_point(&mut self, pixels_per_point: f32) {
80 self.eps = self.class.eps(pixels_per_point);
81 }
82
83 pub fn class(&self) -> Class {
85 self.class
86 }
87
88 pub fn target(&self) -> T {
90 self.rest
91 }
92
93 pub fn curve(&self) -> Curve {
95 self.curve
96 }
97
98 pub fn set(&mut self, value: T) {
100 let lanes = sane_lanes::<T>(value.to_lanes(), &self.target);
101 self.rest_at(lanes, exact(value, lanes));
102 }
103
104 pub fn animate_to(&mut self, target: T, curve: Curve, now: f64, prefs: &MotionPrefs) -> bool {
109 let lanes = sane_lanes::<T>(target.to_lanes(), &self.target);
110 if lanes == self.target {
111 return false;
112 }
113 let now = self.sane_now(now);
114 let (x, v) = self.state(now);
115 self.start(x, v, lanes, exact(target, lanes), curve, now, prefs);
116 true
117 }
118
119 pub fn release(&mut self, velocity: T, target: T, curve: Curve, now: f64, prefs: &MotionPrefs) {
122 let lanes = sane_lanes::<T>(target.to_lanes(), &self.target);
123 let v = sane_lanes::<T>(velocity.to_lanes(), &[0.0; MAX_LANES]);
124 let now = self.sane_now(now);
125 let (x, _) = self.state(now);
126 self.start(x, v, lanes, exact(target, lanes), curve, now, prefs);
127 }
128
129 pub fn sample(&self, now: f64) -> T {
131 if self.is_settled(now) { self.rest } else { T::from_lanes(self.sample_lanes(now)) }
132 }
133
134 pub fn velocity(&self, now: f64) -> T {
137 T::from_lanes(self.velocity_lanes(now))
138 }
139
140 pub fn sample_lanes(&self, now: f64) -> Lanes {
142 self.state(now).0
143 }
144
145 pub fn velocity_lanes(&self, now: f64) -> Lanes {
148 self.state(now).1
149 }
150
151 fn state(&self, now: f64) -> (Lanes, Lanes) {
153 let now = self.sane_now(now);
154 if now >= self.settle_at {
155 return (self.target, [0.0; MAX_LANES]);
156 }
157 let dt = (now - self.t0).max(0.0) as f32;
158 let n = lane_count::<T>();
159 let (mut x, mut v) = self.curve.sample_lanes(&self.from, &self.vel, &self.target, n, dt);
160 if self.class.is_effect() {
161 let lanes = x.iter_mut().zip(v.iter_mut()).zip(self.from.iter().zip(self.target.iter()));
162 for ((x, v), (&from, &target)) in lanes.take(n) {
163 let held = clamp_between(*x, from, target);
164 if held != *x {
165 *x = held;
166 *v = 0.0;
167 }
168 }
169 }
170 (x, v)
171 }
172
173 pub fn settle_at(&self) -> f64 {
176 self.settle_at
177 }
178
179 pub fn is_settled(&self, now: f64) -> bool {
181 self.sane_now(now) >= self.settle_at
182 }
183
184 fn sane_now(&self, now: f64) -> f64 {
185 if now.is_finite() { now } else { self.t0 }
186 }
187
188 fn rest_at(&mut self, lanes: Lanes, rest: T) {
189 self.rest = rest;
190 self.from = lanes;
191 self.target = lanes;
192 self.vel = [0.0; MAX_LANES];
193 self.curve = Curve::Instant;
194 self.settle_at = f64::NEG_INFINITY;
195 }
196
197 fn start(&mut self, x: Lanes, v: Lanes, target: Lanes, rest: T, curve: Curve, now: f64, prefs: &MotionPrefs) {
198 let mut curve = curve.sanitized();
199 if prefs.reduced {
200 curve = self.class.reduce(curve);
201 }
202 if curve.is_instant() {
203 self.rest_at(target, rest);
204 return;
205 }
206 let n = lane_count::<T>();
207 let mut target = target;
208 if matches!(curve, Curve::Decay { .. }) {
209 for (t, (&x, &v)) in target.iter_mut().zip(x.iter().zip(v.iter())).take(n) {
210 *t = curve.rest(x, v, *t);
211 }
212 }
213 let lanes = x.iter().zip(v.iter()).zip(target.iter()).take(n).map(|((&x, &v), &t)| (x, v, t));
214 let settle = curve.settle_time_lanes(lanes, self.eps);
215 let rest = if matches!(curve, Curve::Decay { .. }) { T::from_lanes(target) } else { rest };
217 if settle <= 0.0 {
218 self.rest_at(target, rest);
219 return;
220 }
221 self.rest = rest;
222 self.from = x;
223 self.vel = v;
224 self.target = target;
225 self.curve = curve;
226 self.t0 = now;
227 self.settle_at = now + f64::from(settle);
228 }
229}
230
231fn sane_lanes<T: Animatable>(mut lanes: Lanes, fallback: &Lanes) -> Lanes {
233 let n = lane_count::<T>();
234 for (i, (x, &f)) in lanes.iter_mut().zip(fallback.iter()).enumerate() {
235 if i >= n {
236 *x = 0.0;
237 } else if !x.is_finite() {
238 *x = if f.is_finite() { f } else { 0.0 };
239 }
240 }
241 lanes
242}
243
244fn exact<T: Animatable>(value: T, lanes: Lanes) -> T {
246 let n = lane_count::<T>();
247 if value.to_lanes().get(..n) == lanes.get(..n) { value } else { T::from_lanes(lanes) }
248}
249
250fn clamp_between(x: f32, a: f32, b: f32) -> f32 {
252 let (lo, hi) = if a <= b { (a, b) } else { (b, a) };
253 x.clamp(lo, hi)
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use crate::{Easing, Edges, Spring};
260
261 const P: MotionPrefs = MotionPrefs::DEFAULT;
262
263 #[test]
264 fn linear_tween_from_rest_is_linear() {
265 let mut a = Animated::new(0.0_f32, Class::Spatial);
266 assert!(a.animate_to(100.0, Curve::tween(2.0, Easing::LINEAR), 1.0, &P));
267 for t in [0.0_f64, 0.1, 0.525, 1.0, 1.5, 1.99] {
268 let x = a.sample(1.0 + t);
269 assert!((x - 50.0 * t as f32).abs() < 1e-4, "t={t}: {x}");
270 }
271 assert_eq!(a.sample(3.0), 100.0);
272 }
273
274 #[test]
275 fn lands_exactly_and_settles() {
276 let mut a = Animated::new(0.0_f32, Class::Spatial);
277 assert!(a.is_settled(0.0));
278 assert!(a.animate_to(100.0, Spring::new(0.3, 0.2).into(), 1.0, &P));
279 assert!(!a.animate_to(100.0, Spring::new(0.3, 0.2).into(), 1.1, &P));
280 assert!(!a.is_settled(1.1));
281 let s = a.settle_at();
282 assert!(s > 1.0 && s < 3.0, "{s}");
283 assert_eq!(a.sample(s), 100.0);
284 assert_eq!(a.velocity(s + 1.0), 0.0);
285 assert!(a.is_settled(s));
286 }
287
288 #[test]
289 fn effects_never_overshoot() {
290 let mut a = Animated::new(0.0_f32, Class::Opacity);
291 a.animate_to(1.0, Spring::new(0.3, 0.9).into(), 0.0, &P);
292 let mut t = 0.0;
293 while t < 3.0 {
294 let x = a.sample(t);
295 assert!((0.0..=1.0).contains(&x), "{x}");
296 t += 0.002;
297 }
298 }
299
300 #[test]
301 fn reduced_motion() {
302 let r = MotionPrefs::REDUCED;
303 let mut rect = Animated::new(Edges::new(0.0, 0.0, 10.0, 10.0), Class::Spatial);
304 rect.animate_to(Edges::new(50.0, 0.0, 60.0, 10.0), Curve::DEFAULT, 0.0, &r);
305 assert!(rect.is_settled(0.0));
306 assert_eq!(rect.sample(0.0), Edges::new(50.0, 0.0, 60.0, 10.0));
307 let mut o = Animated::new(0.0_f32, Class::Opacity);
308 o.animate_to(1.0, Spring::new(1.0, 0.0).into(), 0.0, &r);
309 assert!(o.settle_at() <= 0.2);
310 assert!(o.sample(0.09) > 0.0 && o.sample(0.09) < 1.0);
311 let mut g = Animated::new(0.0_f32, Class::Gesture);
312 g.release(500.0, 0.0, Curve::DEFAULT, 0.0, &r);
313 assert!(g.settle_at() > 0.2);
314 }
315
316 #[test]
317 fn decay_coasts_to_projection() {
318 let mut a = Animated::new(10.0_f32, Class::Gesture);
319 a.release(100.0, 0.0, Curve::Decay { k: 5.0 }, 0.0, &P);
320 assert!((a.target() - 30.0).abs() < 1e-4);
321 assert_eq!(a.sample(a.settle_at()), a.target());
322 }
323
324 #[test]
325 fn set_jumps() {
326 let mut a = Animated::new(0.0_f32, Class::Spatial);
327 a.animate_to(10.0, Curve::DEFAULT, 0.0, &P);
328 a.set(3.0);
329 assert!(a.is_settled(0.1));
330 assert_eq!(a.sample(0.1), 3.0);
331 assert_eq!(a.target(), 3.0);
332 }
333}