1use crate::{Spring, Transition};
3use std::time::Duration;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct Inertia {
8 tau: f64,
9 bounds: Option<(f64, f64)>,
10 spring: Spring,
11}
12
13impl Inertia {
14 pub fn new(time_constant: Duration) -> Self {
16 assert!(
17 !time_constant.is_zero(),
18 "inertia time constant must be positive"
19 );
20
21 Self {
22 tau: time_constant.as_secs_f64(),
23 bounds: None,
24 spring: Spring::default(),
25 }
26 }
27
28 pub fn bounds(mut self, min: f64, max: f64) -> Self {
30 assert!(
31 min.is_finite() && max.is_finite() && min <= max,
32 "invalid inertia bounds"
33 );
34 self.bounds = Some((min, max));
35
36 self
37 }
38
39 pub fn spring(mut self, spring: Spring) -> Self {
41 self.spring = spring;
42
43 self
44 }
45
46 pub fn project(self, position: f64, velocity: f64) -> f64 {
48 assert!(
49 position.is_finite() && velocity.is_finite(),
50 "non-finite projection"
51 );
52 let value = finite(position + velocity * self.tau);
53
54 self.bounds
55 .map_or(value, |(min, max)| value.clamp(min, max))
56 }
57}
58
59impl Default for Inertia {
60 fn default() -> Self {
61 Self::new(Duration::from_millis(350))
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
67pub struct ValueFrame {
68 value: f64,
69 velocity: f64,
70 active: bool,
71 completed: bool,
72}
73
74impl ValueFrame {
75 pub fn value(self) -> f64 {
77 self.value
78 }
79
80 pub fn velocity(self) -> f64 {
82 self.velocity
83 }
84
85 pub fn is_animating(self) -> bool {
87 self.active
88 }
89
90 pub fn just_completed(self) -> bool {
92 self.completed
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq)]
97enum Mode {
98 Transition,
99 Inertia(Inertia),
100}
101
102#[derive(Clone, Debug)]
107pub struct MotionValue {
108 value: f64,
109 velocity: f64,
110 from: f64,
111 initial_velocity: f64,
112 target: f64,
113 transition: Transition,
114 delay: Duration,
115 started: Duration,
116 last: Duration,
117 set_time: Duration,
118 active: bool,
119 completed: bool,
120 mode: Mode,
121}
122
123impl MotionValue {
124 pub fn new(value: f64) -> Self {
126 assert!(value.is_finite(), "motion value must be finite");
127
128 Self {
129 value,
130 velocity: 0.0,
131 from: value,
132 initial_velocity: 0.0,
133 target: value,
134 transition: Transition::Instant,
135 delay: Duration::ZERO,
136 started: Duration::ZERO,
137 last: Duration::ZERO,
138 set_time: Duration::ZERO,
139 active: false,
140 completed: false,
141 mode: Mode::Transition,
142 }
143 }
144
145 pub fn get(&self) -> f64 {
147 self.value
148 }
149
150 pub(crate) fn active(&self) -> bool {
151 self.active
152 }
153
154 pub(crate) fn target(&self) -> f64 {
155 self.target
156 }
157
158 pub(crate) fn matches(&self, target: f64, transition: Transition) -> bool {
159 self.mode == Mode::Transition
160 && self.target == target
161 && self.transition == transition
162 && self.delay.is_zero()
163 }
164
165 #[cfg(feature = "text")]
166 pub(crate) fn translate(&mut self, offset: f64) {
167 debug_assert_eq!(self.mode, Mode::Transition);
168 self.value = finite(self.value + offset);
169 self.from = finite(self.from + offset);
170 self.target = finite(self.target + offset);
171 }
172
173 pub fn rescale(&mut self, factor: f64) {
178 assert!(factor.is_finite() && factor > 0.0, "invalid unit scale");
179 self.value = finite(self.value * factor);
180 self.velocity = finite(self.velocity * factor);
181 self.from = finite(self.from * factor);
182 self.initial_velocity = finite(self.initial_velocity * factor);
183 self.target = finite(self.target * factor);
184
185 if let Mode::Inertia(ref mut inertia) = self.mode {
186 inertia.bounds = inertia
187 .bounds
188 .map(|(min, max)| (finite(min * factor), finite(max * factor)));
189 }
190 }
191
192 pub fn velocity(&self) -> f64 {
194 self.velocity
195 }
196
197 pub fn set(&mut self, value: f64, now: Duration) {
199 assert!(value.is_finite(), "motion value must be finite");
200 let now = now.max(self.last);
201 let dt = now.saturating_sub(self.set_time).as_secs_f64();
202
203 self.velocity = if dt > 0.0 {
204 finite((value - self.value) / dt)
205 } else {
206 self.velocity
207 };
208 self.value = value;
209 self.target = value;
210 self.from = value;
211 self.active = false;
212 self.completed = false;
213 self.last = now;
214 self.set_time = now;
215 self.mode = Mode::Transition;
216 }
217
218 pub fn jump(&mut self, value: f64, now: Duration) {
220 self.set(value, now);
221 self.velocity = 0.0;
222 }
223
224 pub fn animate(&mut self, target: f64, transition: Transition, now: Duration) {
226 self.animate_delayed(target, transition, Duration::ZERO, now);
227 }
228
229 pub fn animate_delayed(
231 &mut self,
232 target: f64,
233 transition: Transition,
234 delay: Duration,
235 now: Duration,
236 ) {
237 assert!(target.is_finite(), "motion target must be finite");
238 let now = now.max(self.last);
239
240 if self.mode == Mode::Transition
241 && self.target == target
242 && self.transition == transition
243 && self.delay == delay
244 {
245 return;
246 }
247
248 self.advance(now);
249 self.from = self.value;
250 self.initial_velocity = self.velocity;
251 self.target = target;
252 self.transition = transition;
253 self.delay = delay;
254 self.started = now;
255 self.active = self.from != target || self.velocity != 0.0;
256 self.mode = Mode::Transition;
257 self.completed = false;
258 }
259
260 pub fn coast(&mut self, inertia: Inertia, now: Duration) {
263 let now = now.max(self.last);
264
265 self.advance(now);
266 self.from = self.value;
267 self.initial_velocity = self.velocity;
268 self.target = inertia.project(self.value, self.velocity);
269 self.started = now;
270 self.delay = Duration::ZERO;
271 self.mode = Mode::Inertia(inertia);
272 self.active = self.value != self.target || self.velocity != 0.0;
273 self.completed = false;
274 }
275
276 pub fn sample(&mut self, now: Duration, reduced_motion: bool) -> ValueFrame {
278 if reduced_motion && self.active {
279 self.finish();
280 } else {
281 self.advance(now.max(self.last));
282 }
283
284 self.last = self.last.max(now);
285 let completed = std::mem::take(&mut self.completed);
286
287 ValueFrame {
288 value: self.value,
289 velocity: self.velocity,
290 active: self.active,
291 completed,
292 }
293 }
294
295 fn finish(&mut self) {
296 self.value = self.target;
297 self.velocity = 0.0;
298 self.completed |= self.active;
299 self.active = false;
300 }
301
302 fn advance(&mut self, now: Duration) {
303 self.last = now;
304
305 if !self.active {
306 return;
307 }
308
309 let elapsed = now.saturating_sub(self.started);
310
311 if elapsed < self.delay {
312 self.velocity = 0.0;
313
314 return;
315 }
316
317 let t = elapsed.saturating_sub(self.delay).as_secs_f64();
318
319 let (value, velocity, done) = match self.mode {
320 Mode::Transition => sample_transition(
321 self.from,
322 self.initial_velocity,
323 self.target,
324 self.transition,
325 t,
326 ),
327 Mode::Inertia(config) => {
328 let amplitude = finite(self.initial_velocity * config.tau);
329 let end = finite(self.from + amplitude);
330
331 let boundary = config.bounds.and_then(|(min, max)| {
332 if self.from < min {
333 Some((min, 0.0))
334 } else if self.from > max {
335 Some((max, 0.0))
336 } else if end < min || end > max {
337 let bound = if end < min { min } else { max };
338 let crossing = -config.tau * (1.0 - (bound - self.from) / amplitude).ln();
339
340 Some((bound, crossing.max(0.0)))
341 } else {
342 None
343 }
344 });
345
346 if let Some((bound, crossing)) = boundary
347 && t >= crossing
348 {
349 let from = if crossing == 0.0 { self.from } else { bound };
350 let velocity = self.initial_velocity * (-crossing / config.tau).exp();
351
352 sample_transition(
353 from,
354 velocity,
355 bound,
356 Transition::Spring(config.spring),
357 t - crossing,
358 )
359 } else {
360 let decay = (-t / config.tau).exp();
361 let value = finite(self.from + amplitude * (1.0 - decay));
362 let velocity = finite(self.initial_velocity * decay);
363
364 (
365 value,
366 velocity,
367 (end - value).abs() <= 0.001 && velocity.abs() <= 0.001,
368 )
369 }
370 }
371 };
372
373 self.value = value;
374 self.velocity = velocity;
375 self.set_time = now;
376
377 if done {
378 self.finish();
379 }
380 }
381}
382
383pub(crate) fn sample_transition(
384 from: f64,
385 velocity: f64,
386 target: f64,
387 transition: Transition,
388 t: f64,
389) -> (f64, f64, bool) {
390 match transition {
391 Transition::Instant => (target, 0.0, true),
392 Transition::Tween(duration, easing) => {
393 let seconds = duration.as_secs_f64();
394
395 if t >= seconds {
396 return (target, 0.0, true);
397 }
398
399 let (progress, derivative) = easing.sample(t / seconds);
400
401 (
402 if t == 0.0 {
403 from
404 } else {
405 finite(from * (1.0 - progress) + target * progress)
406 },
407 finite(finite(target - from) * derivative / seconds),
408 false,
409 )
410 }
411 Transition::Spring(spring) => {
412 if t == 0.0 {
413 return (from, velocity, false);
414 }
415
416 let m = spring.matrix(t);
417 let displacement = finite(from - target);
418 let value = finite(target + m[0] * displacement + m[1] * velocity);
419 let velocity = finite(m[2] * displacement + m[3] * velocity);
420
421 (
422 value,
423 velocity,
424 spring.is_settled(value, velocity, target, 0.001),
425 )
426 }
427 }
428}
429
430pub(crate) fn finite(value: f64) -> f64 {
431 if value.is_nan() {
432 0.0
433 } else {
434 value.clamp(-f64::MAX, f64::MAX)
435 }
436}