use std::time::Duration; use web_time::Instant; use crate::motion; /// Values gliding from where they were to where the owner put them. #[derive(Default)] pub(crate) struct Glide { from: Vec>, to: Vec>, since: Option, } impl Glide { /// The values at this moment, and whether they still move: a change glides from wherever the last glide stood; a new shape snaps. pub(crate) fn follow( &mut self, values: &[Vec], length: Duration, ) -> (Vec>, bool) { let share = |since: Option| { since.map_or(1.0, |since| { (since.elapsed().as_secs_f32() / length.as_secs_f32()).min(1.0) }) }; if self.to != values { let now = if same_shape(&self.from, &self.to) { mix( &self.from, &self.to, motion::ease_out_cubic(share(self.since)), ) } else { self.to.clone() }; let glides = !self.to.is_empty() && same_shape(&now, values); self.from = if glides { now } else { values.to_vec() }; self.to = values.to_vec(); self.since = glides.then(Instant::now); } let t = share(self.since); if t < 1.0 { (mix(&self.from, &self.to, motion::ease_out_cubic(t)), true) } else { (self.to.clone(), false) } } } /// Values `share` of the way from `from` to `to`. fn mix(from: &[Vec], to: &[Vec], share: f32) -> Vec> { let share = f64::from(share); to.iter() .zip(from) .map(|(to, from)| { to.iter() .zip(from) .map(|(b, a)| a + (b - a) * share) .collect() }) .collect() } fn same_shape(a: &[Vec], b: &[Vec]) -> bool { a.len() == b.len() && a.iter().zip(b).all(|(a, b)| a.len() == b.len()) } #[cfg(test)] mod tests { use super::*; #[test] fn a_glide_starts_still_then_moves_toward_a_change() { let (from, to) = (vec![vec![0.0, 10.0]], vec![vec![10.0, 20.0]]); assert_eq!(mix(&from, &to, 0.5), [vec![5.0, 15.0]]); let mut glide = Glide::default(); let long = Duration::from_secs(60); assert_eq!( glide.follow(&from, long), (from.clone(), false), "the first values hold still" ); let (now, moving) = glide.follow(&to, long); assert!( moving && now[0][0] < 1.0, "a change starts from the old values, got {now:?}" ); assert_eq!( glide.follow(&[vec![1.0]], long), (vec![vec![1.0]], false), "a new shape snaps" ); } }