use crate::widgets::WidgetState;
use std::collections::VecDeque;
#[derive(Clone, Copy)]
pub struct Spring {
pub stiffness: f32,
pub damping: f32,
}
impl Spring {
pub const BOUNCY: Self = Self {
stiffness: 300.0,
damping: 18.0,
};
}
pub struct SpringValue {
value: f32,
velocity: f32,
spring: Spring,
}
impl SpringValue {
pub const fn new(initial: f32, spring: Spring) -> Self {
Self {
value: initial,
velocity: 0.0,
spring,
}
}
pub fn update(&mut self, target: f32, dt: f32) {
let force = (-self.spring.stiffness)
.mul_add(self.value - target, -(self.spring.damping * self.velocity));
self.velocity += force * dt;
self.value += self.velocity * dt;
if (self.value - target).abs() < 0.001 && self.velocity.abs() < 0.001 {
self.value = target;
self.velocity = 0.0;
}
}
pub const fn t(&self) -> f32 {
self.value.clamp(0.0, 1.5)
}
}
pub struct TrailBuffer {
entries: VecDeque<(f32, f32, f32)>, capacity_secs: f32,
}
impl TrailBuffer {
pub const fn new(capacity_secs: f32) -> Self {
Self {
entries: VecDeque::new(),
capacity_secs,
}
}
pub fn push(&mut self, now: f32, x: f32, y: f32) {
self.entries.push_back((now, x, y));
while let Some(&(t, _, _)) = self.entries.front() {
if now - t > self.capacity_secs + 0.1 {
self.entries.pop_front();
} else {
break;
}
}
}
pub fn sample(&self, now: f32, trail: f32) -> (f32, f32) {
let target_t = now - trail;
let mut before = None;
let mut after = None;
for &entry in &self.entries {
let (t, _, _) = entry;
if t <= target_t {
before = Some(entry);
} else if after.is_none() {
after = Some(entry);
break;
}
}
match (before, after) {
(Some((t0, x0, y0)), Some((t1, x1, y1))) => {
let span = (t1 - t0).max(f32::EPSILON);
let f = ((target_t - t0) / span).clamp(0.0, 1.0);
((x1 - x0).mul_add(f, x0), (y1 - y0).mul_add(f, y0))
}
(Some((_, x, y)), None) | (None, Some((_, x, y))) => (x, y),
(None, None) => (0.0, 0.0),
}
}
}
pub struct Anim {
from: WidgetState,
to: WidgetState,
spring: Spring,
position: f32, velocity: f32, done: bool, }
impl Anim {
pub fn new(spring: Spring) -> Self {
Self {
from: WidgetState::default(),
to: WidgetState::default(),
spring,
position: 1.0,
velocity: 0.0,
done: true,
}
}
pub fn transition(&mut self, to: WidgetState) {
if self.to == to {
return;
}
self.from = self.to;
self.to = to;
self.position = 1.0 - self.position.clamp(0.0, 1.5); self.velocity = -self.velocity; self.done = false;
}
pub fn update(&mut self, dt: f32) {
if self.done {
return;
}
let target = 1.0_f32;
let displacement = self.position - target;
let force =
(-self.spring.stiffness).mul_add(displacement, -(self.spring.damping * self.velocity));
self.velocity += force * dt;
self.position += self.velocity * dt;
if (self.position - target).abs() < 0.001 && self.velocity.abs() < 0.001 {
self.position = target;
self.velocity = 0.0;
self.done = true;
}
}
pub const fn t(&self) -> f32 {
self.position.clamp(0.0, 1.5) }
pub const fn prev_state(&self) -> WidgetState {
self.from
}
}