use crate::{Spring, Transition};
use std::time::Duration;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Inertia {
tau: f64,
bounds: Option<(f64, f64)>,
spring: Spring,
}
impl Inertia {
pub fn new(time_constant: Duration) -> Self {
assert!(
!time_constant.is_zero(),
"inertia time constant must be positive"
);
Self {
tau: time_constant.as_secs_f64(),
bounds: None,
spring: Spring::default(),
}
}
pub fn bounds(mut self, min: f64, max: f64) -> Self {
assert!(
min.is_finite() && max.is_finite() && min <= max,
"invalid inertia bounds"
);
self.bounds = Some((min, max));
self
}
pub fn spring(mut self, spring: Spring) -> Self {
self.spring = spring;
self
}
pub fn project(self, position: f64, velocity: f64) -> f64 {
assert!(
position.is_finite() && velocity.is_finite(),
"non-finite projection"
);
let value = finite(position + velocity * self.tau);
self.bounds
.map_or(value, |(min, max)| value.clamp(min, max))
}
}
impl Default for Inertia {
fn default() -> Self {
Self::new(Duration::from_millis(350))
}
}
#[derive(Clone, Copy, Debug)]
pub struct ValueFrame {
value: f64,
velocity: f64,
active: bool,
completed: bool,
}
impl ValueFrame {
pub fn value(self) -> f64 {
self.value
}
pub fn velocity(self) -> f64 {
self.velocity
}
pub fn is_animating(self) -> bool {
self.active
}
pub fn just_completed(self) -> bool {
self.completed
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Mode {
Transition,
Inertia(Inertia),
}
#[derive(Clone, Debug)]
pub struct MotionValue {
value: f64,
velocity: f64,
from: f64,
initial_velocity: f64,
target: f64,
transition: Transition,
delay: Duration,
started: Duration,
last: Duration,
set_time: Duration,
active: bool,
completed: bool,
mode: Mode,
}
impl MotionValue {
pub fn new(value: f64) -> Self {
assert!(value.is_finite(), "motion value must be finite");
Self {
value,
velocity: 0.0,
from: value,
initial_velocity: 0.0,
target: value,
transition: Transition::Instant,
delay: Duration::ZERO,
started: Duration::ZERO,
last: Duration::ZERO,
set_time: Duration::ZERO,
active: false,
completed: false,
mode: Mode::Transition,
}
}
pub fn get(&self) -> f64 {
self.value
}
pub(crate) fn active(&self) -> bool {
self.active
}
pub(crate) fn target(&self) -> f64 {
self.target
}
pub(crate) fn matches(&self, target: f64, transition: Transition) -> bool {
self.mode == Mode::Transition
&& self.target == target
&& self.transition == transition
&& self.delay.is_zero()
}
#[cfg(feature = "text")]
pub(crate) fn translate(&mut self, offset: f64) {
debug_assert_eq!(self.mode, Mode::Transition);
self.value = finite(self.value + offset);
self.from = finite(self.from + offset);
self.target = finite(self.target + offset);
}
pub fn rescale(&mut self, factor: f64) {
assert!(factor.is_finite() && factor > 0.0, "invalid unit scale");
self.value = finite(self.value * factor);
self.velocity = finite(self.velocity * factor);
self.from = finite(self.from * factor);
self.initial_velocity = finite(self.initial_velocity * factor);
self.target = finite(self.target * factor);
if let Mode::Inertia(ref mut inertia) = self.mode {
inertia.bounds = inertia
.bounds
.map(|(min, max)| (finite(min * factor), finite(max * factor)));
}
}
pub fn velocity(&self) -> f64 {
self.velocity
}
pub fn set(&mut self, value: f64, now: Duration) {
assert!(value.is_finite(), "motion value must be finite");
let now = now.max(self.last);
let dt = now.saturating_sub(self.set_time).as_secs_f64();
self.velocity = if dt > 0.0 {
finite((value - self.value) / dt)
} else {
self.velocity
};
self.value = value;
self.target = value;
self.from = value;
self.active = false;
self.completed = false;
self.last = now;
self.set_time = now;
self.mode = Mode::Transition;
}
pub fn jump(&mut self, value: f64, now: Duration) {
self.set(value, now);
self.velocity = 0.0;
}
pub fn animate(&mut self, target: f64, transition: Transition, now: Duration) {
self.animate_delayed(target, transition, Duration::ZERO, now);
}
pub fn animate_delayed(
&mut self,
target: f64,
transition: Transition,
delay: Duration,
now: Duration,
) {
assert!(target.is_finite(), "motion target must be finite");
let now = now.max(self.last);
if self.mode == Mode::Transition
&& self.target == target
&& self.transition == transition
&& self.delay == delay
{
return;
}
self.advance(now);
self.from = self.value;
self.initial_velocity = self.velocity;
self.target = target;
self.transition = transition;
self.delay = delay;
self.started = now;
self.active = self.from != target || self.velocity != 0.0;
self.mode = Mode::Transition;
self.completed = false;
}
pub fn coast(&mut self, inertia: Inertia, now: Duration) {
let now = now.max(self.last);
self.advance(now);
self.from = self.value;
self.initial_velocity = self.velocity;
self.target = inertia.project(self.value, self.velocity);
self.started = now;
self.delay = Duration::ZERO;
self.mode = Mode::Inertia(inertia);
self.active = self.value != self.target || self.velocity != 0.0;
self.completed = false;
}
pub fn sample(&mut self, now: Duration, reduced_motion: bool) -> ValueFrame {
if reduced_motion && self.active {
self.finish();
} else {
self.advance(now.max(self.last));
}
self.last = self.last.max(now);
let completed = std::mem::take(&mut self.completed);
ValueFrame {
value: self.value,
velocity: self.velocity,
active: self.active,
completed,
}
}
fn finish(&mut self) {
self.value = self.target;
self.velocity = 0.0;
self.completed |= self.active;
self.active = false;
}
fn advance(&mut self, now: Duration) {
self.last = now;
if !self.active {
return;
}
let elapsed = now.saturating_sub(self.started);
if elapsed < self.delay {
self.velocity = 0.0;
return;
}
let t = elapsed.saturating_sub(self.delay).as_secs_f64();
let (value, velocity, done) = match self.mode {
Mode::Transition => sample_transition(
self.from,
self.initial_velocity,
self.target,
self.transition,
t,
),
Mode::Inertia(config) => {
let amplitude = finite(self.initial_velocity * config.tau);
let end = finite(self.from + amplitude);
let boundary = config.bounds.and_then(|(min, max)| {
if self.from < min {
Some((min, 0.0))
} else if self.from > max {
Some((max, 0.0))
} else if end < min || end > max {
let bound = if end < min { min } else { max };
let crossing = -config.tau * (1.0 - (bound - self.from) / amplitude).ln();
Some((bound, crossing.max(0.0)))
} else {
None
}
});
if let Some((bound, crossing)) = boundary
&& t >= crossing
{
let from = if crossing == 0.0 { self.from } else { bound };
let velocity = self.initial_velocity * (-crossing / config.tau).exp();
sample_transition(
from,
velocity,
bound,
Transition::Spring(config.spring),
t - crossing,
)
} else {
let decay = (-t / config.tau).exp();
let value = finite(self.from + amplitude * (1.0 - decay));
let velocity = finite(self.initial_velocity * decay);
(
value,
velocity,
(end - value).abs() <= 0.001 && velocity.abs() <= 0.001,
)
}
}
};
self.value = value;
self.velocity = velocity;
self.set_time = now;
if done {
self.finish();
}
}
}
pub(crate) fn sample_transition(
from: f64,
velocity: f64,
target: f64,
transition: Transition,
t: f64,
) -> (f64, f64, bool) {
match transition {
Transition::Instant => (target, 0.0, true),
Transition::Tween(duration, easing) => {
let seconds = duration.as_secs_f64();
if t >= seconds {
return (target, 0.0, true);
}
let (progress, derivative) = easing.sample(t / seconds);
(
if t == 0.0 {
from
} else {
finite(from * (1.0 - progress) + target * progress)
},
finite(finite(target - from) * derivative / seconds),
false,
)
}
Transition::Spring(spring) => {
if t == 0.0 {
return (from, velocity, false);
}
let m = spring.matrix(t);
let displacement = finite(from - target);
let value = finite(target + m[0] * displacement + m[1] * velocity);
let velocity = finite(m[2] * displacement + m[3] * velocity);
(
value,
velocity,
spring.is_settled(value, velocity, target, 0.001),
)
}
}
}
pub(crate) fn finite(value: f64) -> f64 {
if value.is_nan() {
0.0
} else {
value.clamp(-f64::MAX, f64::MAX)
}
}