use std::{
f32::consts::{FRAC_PI_2, TAU},
fmt, ops,
};
use crate::impl_from_and_into_var;
use super::*;
pub type EasingStep = Factor;
#[derive(Debug, PartialEq, Copy, Clone, Hash, PartialOrd)]
pub struct EasingTime(Factor);
impl_from_and_into_var! {
fn from(factor: Factor) -> EasingTime {
EasingTime::new(factor)
}
fn from(factor: FactorPercent) -> EasingTime {
EasingTime::new(factor.fct())
}
}
impl EasingTime {
pub fn new(factor: Factor) -> Self {
EasingTime(factor.clamp_range())
}
pub fn elapsed(duration: Duration, elapsed: Duration, time_scale: Factor) -> Self {
EasingTime::new(elapsed.as_secs_f32().fct() / duration.as_secs_f32().fct() * time_scale)
}
pub fn start() -> Self {
EasingTime(0.fct())
}
pub fn end() -> Self {
EasingTime(1.fct())
}
pub fn is_start(self) -> bool {
self == Self::start()
}
pub fn is_end(self) -> bool {
self == Self::end()
}
pub fn fct(self) -> Factor {
self.0
}
pub fn pct(self) -> FactorPercent {
self.0.0.pct()
}
pub fn reverse(self) -> Self {
EasingTime(self.0.flip())
}
pub fn seg<T: Into<EasingTime> + Clone>(self, segment_range: impl ops::RangeBounds<T>) -> Option<EasingTime> {
const MIN_LENGTH: f32 = EQ_GRANULARITY + 0.00001;
let start = match segment_range.start_bound() {
ops::Bound::Included(t) => t.clone().into().fct(),
ops::Bound::Excluded(t) => t.clone().into().fct() + MIN_LENGTH.fct(),
ops::Bound::Unbounded => 0.fct(),
};
let end = match segment_range.end_bound() {
ops::Bound::Included(t) => t.clone().into().fct(),
ops::Bound::Excluded(t) => t.clone().into().fct() - MIN_LENGTH.fct(),
ops::Bound::Unbounded => 1.fct(),
};
self.seg_impl(start..=end)
}
fn seg_impl(self, s: ops::RangeInclusive<Factor>) -> Option<EasingTime> {
let len = *s.end() - *s.start();
let v = self.0 - *s.start();
if v >= 0.fct() && v <= len {
Some(EasingTime(v / len))
} else {
None
}
}
}
impl ops::Add for EasingTime {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 + rhs.0)
}
}
impl ops::AddAssign for EasingTime {
fn add_assign(&mut self, rhs: Self) {
self.0 += rhs.0;
}
}
impl ops::Sub for EasingTime {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 - rhs.0)
}
}
impl ops::SubAssign for EasingTime {
fn sub_assign(&mut self, rhs: Self) {
self.0 -= rhs.0;
}
}
#[derive(Clone)]
pub enum EasingFn {
Linear,
Sine,
Quad,
Cubic,
Quart,
Quint,
Expo,
Circ,
Back,
Elastic,
Bounce,
None,
Custom(Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>),
}
impl PartialEq for EasingFn {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl Eq for EasingFn {}
impl fmt::Debug for EasingFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Linear => write!(f, "linear"),
Self::Sine => write!(f, "sine"),
Self::Quad => write!(f, "quad"),
Self::Cubic => write!(f, "cubic"),
Self::Quart => write!(f, "quart"),
Self::Quint => write!(f, "quint"),
Self::Expo => write!(f, "expo"),
Self::Circ => write!(f, "circ"),
Self::Back => write!(f, "back"),
Self::Elastic => write!(f, "elastic"),
Self::Bounce => write!(f, "bounce"),
Self::None => write!(f, "none"),
Self::Custom(_) => f.debug_tuple("Custom").finish(),
}
}
}
impl EasingFn {
pub fn ease_fn(&self) -> impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static {
let me = self.clone();
move |t| me(t)
}
pub fn custom(f: impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
Self::Custom(Arc::new(f))
}
pub fn modified(self, modifier: impl Fn(&dyn Fn(EasingTime) -> EasingStep, EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
Self::custom(move |t| modifier(&*self, t))
}
pub fn ease_out(self) -> Self {
self.modified(|f, t| easing::ease_out(f, t))
}
pub fn ease_in_out(self) -> Self {
self.modified(|f, t| easing::ease_in_out(f, t))
}
pub fn ease_out_in(self) -> Self {
self.modified(|f, t| easing::ease_out_in(f, t))
}
pub fn reverse(self) -> Self {
self.modified(|f, t| easing::reverse(f, t))
}
pub fn reverse_out(self) -> Self {
self.modified(|f, t| easing::reverse_out(f, t))
}
}
impl ops::Deref for EasingFn {
type Target = dyn Fn(EasingTime) -> EasingStep + Send + Sync;
fn deref(&self) -> &Self::Target {
match self {
EasingFn::Linear => &easing::linear,
EasingFn::Sine => &easing::sine,
EasingFn::Quad => &easing::quad,
EasingFn::Cubic => &easing::cubic,
EasingFn::Quart => &easing::quad,
EasingFn::Quint => &easing::quint,
EasingFn::Expo => &easing::expo,
EasingFn::Circ => &easing::circ,
EasingFn::Back => &easing::back,
EasingFn::Elastic => &easing::elastic,
EasingFn::Bounce => &easing::bounce,
EasingFn::None => &easing::none,
EasingFn::Custom(c) => &**c,
}
}
}
pub fn linear(time: EasingTime) -> EasingStep {
time.fct()
}
pub fn quad(time: EasingTime) -> EasingStep {
let f = time.fct();
f * f
}
pub fn cubic(time: EasingTime) -> EasingStep {
let f = time.fct();
f * f * f
}
pub fn quart(time: EasingTime) -> EasingStep {
let f = time.fct();
f * f * f * f
}
pub fn quint(time: EasingTime) -> EasingStep {
let f = time.fct();
f * f * f * f * f
}
pub fn sine(time: EasingTime) -> EasingStep {
let f = time.fct().0;
(1.0 - (f * FRAC_PI_2).cos()).fct()
}
pub fn expo(time: EasingTime) -> EasingStep {
let f = time.fct();
if f == 0.fct() {
0.fct()
} else {
2.0_f32.powf(10.0 * f.0 - 10.0).fct()
}
}
pub fn circ(time: EasingTime) -> EasingStep {
let f = time.fct().0;
(1.0 - (1.0 - f.powf(2.0)).sqrt()).fct()
}
pub fn back(time: EasingTime) -> EasingStep {
let f = time.fct().0;
(f * f * (2.70158 * f - 1.70158)).fct()
}
pub fn elastic(time: EasingTime) -> EasingStep {
let t = time.fct();
const C: f32 = TAU / 3.0;
if t == 0.fct() || t == 1.fct() {
t
} else {
let t = t.0;
let s = -(2.0_f32.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * C).sin();
s.fct()
}
}
pub fn bounce(time: EasingTime) -> EasingStep {
const N: f32 = 7.5625;
const D: f32 = 2.75;
let mut t = 1.0 - time.fct().0;
let f = if t < 1.0 / D {
N * t * t
} else if t < 2.0 / D {
t -= 1.5 / D;
N * t * t + 0.75
} else if t < 2.5 / D {
t -= 2.25 / D;
N * t * t + 0.9375
} else {
t -= 2.625 / D;
N * t * t + 0.984375
};
(1.0 - f).fct()
}
pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32, time: EasingTime) -> EasingStep {
let f = time.fct().0 as f64;
(Bezier::new(x1, y1, x2, y2).solve(f, 0.00001) as f32).fct()
}
pub fn step_ceil(steps: u32, time: EasingTime) -> EasingStep {
let steps = steps as f32;
let step = (steps * time.fct().0).ceil();
(step / steps).fct()
}
pub fn step_floor(steps: u32, time: EasingTime) -> EasingStep {
let steps = steps as f32;
let step = (steps * time.fct().0).floor();
(step / steps).fct()
}
pub fn none(_: EasingTime) -> EasingStep {
1.fct()
}
pub fn ease_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
ease_fn(time)
}
pub fn ease_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
ease_fn(time.reverse()).flip()
}
pub fn ease_in_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
let t = time.fct();
if t <= 0.5.fct() {
ease_in(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
} else {
ease_out(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
}
}
pub fn ease_out_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
let t = time.fct();
if t <= 0.5.fct() {
ease_out(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
} else {
ease_in(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
}
}
pub fn reverse(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
ease_fn(time.reverse())
}
pub fn reverse_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
ease_fn(time).flip()
}
pub use bezier::*;
use zng_unit::{EQ_GRANULARITY, FactorPercent, FactorUnits as _};
mod bezier {
const NEWTON_METHOD_ITERATIONS: u8 = 8;
pub struct Bezier {
ax: f64,
bx: f64,
cx: f64,
ay: f64,
by: f64,
cy: f64,
}
impl Bezier {
pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Bezier {
let cx = 3. * x1 as f64;
let bx = 3. * (x2 as f64 - x1 as f64) - cx;
let cy = 3. * y1 as f64;
let by = 3. * (y2 as f64 - y1 as f64) - cy;
Bezier {
ax: 1.0 - cx - bx,
bx,
cx,
ay: 1.0 - cy - by,
by,
cy,
}
}
fn sample_curve_x(&self, t: f64) -> f64 {
((self.ax * t + self.bx) * t + self.cx) * t
}
fn sample_curve_y(&self, t: f64) -> f64 {
((self.ay * t + self.by) * t + self.cy) * t
}
fn sample_curve_derivative_x(&self, t: f64) -> f64 {
(3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
}
fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
let mut t = x;
for _ in 0..NEWTON_METHOD_ITERATIONS {
let x2 = self.sample_curve_x(t);
if x2.approx_eq(x, epsilon) {
return t;
}
let dx = self.sample_curve_derivative_x(t);
if dx.approx_eq(0.0, 1e-6) {
break;
}
t -= (x2 - x) / dx;
}
let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
if t < lo {
return lo;
}
if t > hi {
return hi;
}
while lo < hi {
let x2 = self.sample_curve_x(t);
if x2.approx_eq(x, epsilon) {
return t;
}
if x > x2 {
lo = t
} else {
hi = t
}
t = (hi - lo) / 2.0 + lo
}
t
}
pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
self.sample_curve_y(self.solve_curve_x(x, epsilon))
}
}
trait ApproxEq {
fn approx_eq(self, value: Self, epsilon: Self) -> bool;
}
impl ApproxEq for f64 {
fn approx_eq(self, value: f64, epsilon: f64) -> bool {
(self - value).abs() < epsilon
}
}
}