use std::sync::{Arc, Mutex};
use blinc_animation::{SharedAnimatedValue, SpringConfig};
struct State {
from: Vec<Vec<f64>>,
to: Vec<Vec<f64>>,
seen: Option<Vec<Vec<f64>>>,
}
pub(super) struct Easer {
state: Mutex<State>,
progress: Option<SharedAnimatedValue>,
sweep_in: bool,
}
impl Easer {
pub fn new(sweep_in: bool) -> Self {
let progress = blinc_layout::render_state::get_global_scheduler().map(|handle| {
Arc::new(Mutex::new(blinc_animation::AnimatedValue::new(
handle,
1.0,
SpringConfig::new(120.0, 22.0, 1.0),
)))
});
Self {
state: Mutex::new(State {
from: Vec::new(),
to: Vec::new(),
seen: None,
}),
progress,
sweep_in,
}
}
fn progress(&self) -> f64 {
self.progress
.as_ref()
.map_or(1.0, |p| p.lock().unwrap().get().clamp(0.0, 1.0) as f64)
}
pub fn note(&self, target: &[Vec<f64>]) {
let mut s = self.state.lock().unwrap();
if s.seen.as_deref() == Some(target) {
return;
}
let first = s.seen.is_none();
s.from = if first && !self.sweep_in {
target.to_vec()
} else {
blend(&s.from, &s.to, self.progress())
};
s.to = target.to_vec();
s.seen = Some(target.to_vec());
if let Some(p) = &self.progress {
let mut p = p.lock().unwrap();
p.set_immediate(0.0);
p.set_target(1.0);
}
}
pub fn shown(&self) -> Vec<Vec<f64>> {
let s = self.state.lock().unwrap();
blend(&s.from, &s.to, self.progress())
}
pub fn wants_frames(&self, target: Option<&[Vec<f64>]>) -> bool {
let easing = self
.progress
.as_ref()
.is_some_and(|p| p.lock().unwrap().is_animating());
easing || self.state.lock().unwrap().seen.as_deref() != target
}
}
fn blend(from: &[Vec<f64>], to: &[Vec<f64>], t: f64) -> Vec<Vec<f64>> {
to.iter()
.enumerate()
.map(|(s, row)| {
row.iter()
.enumerate()
.map(|(i, b)| {
let a = from.get(s).and_then(|r| r.get(i)).copied().unwrap_or(0.0);
a + (b - a) * t
})
.collect()
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn values_blend_towards_the_new_ones() {
let from = vec![vec![0.0, 10.0]];
let to = vec![vec![10.0, 20.0, 30.0]];
assert_eq!(blend(&from, &to, 0.5), vec![vec![5.0, 15.0, 15.0]]);
}
#[test]
fn without_a_scheduler_new_values_show_at_once() {
let easer = Easer::new(true);
if easer.progress.is_some() {
return;
}
let (first, next) = (vec![vec![4.0]], vec![vec![9.0, 1.0]]);
easer.note(&first);
assert_eq!(easer.shown(), first);
easer.note(&next);
assert_eq!(easer.shown(), next);
assert!(!easer.wants_frames(Some(&next)));
assert!(easer.wants_frames(Some(&first)), "other data is new");
}
}