///|
/// Lifecycle state of a tween sample.
pub(all) enum TweenState {
Before
Active
After
} derive(Eq, Debug)
///|
/// A value sample with enough metadata for a renderer or UI state machine.
pub(all) struct TweenSample {
time : Double
value : Double
progress : Double
cycle : Int
state : TweenState
} derive(Debug)
///|
pub fn TweenSample::value(self : TweenSample) -> Double {
self.value
}
///|
pub fn TweenSample::progress(self : TweenSample) -> Double {
self.progress
}
///|
pub fn TweenSample::cycle(self : TweenSample) -> Int {
self.cycle
}
///|
pub fn TweenSample::state(self : TweenSample) -> TweenState {
self.state
}
///|
/// A scalar tween with delay, repeats, and optional ping-pong direction.
pub struct Tween {
from : Double
to : Double
duration : Double
delay : Double
repeat_count : Int
mode : Extrapolation
curve : Curve
} derive(Debug)
///|
pub fn Tween::new(
from : Double,
to : Double,
duration : Double,
delay? : Double = 0.0,
repeat_count? : Int = 0,
mode? : Extrapolation = Clamp,
curve? : Curve = Curve::builtin(Linear),
) -> Tween raise MotionError {
ensure_finite(from)
ensure_finite(to)
if duration <= 0.0 {
raise MotionError::InvalidDuration(duration)
}
if delay < 0.0 {
raise MotionError::InvalidDelay(delay)
}
if repeat_count < 0 {
raise MotionError::InvalidRepeatCount(repeat_count)
}
{ from, to, duration, delay, repeat_count, mode, curve }
}
///|
pub fn Tween::from(self : Tween) -> Double {
self.from
}
///|
pub fn Tween::to(self : Tween) -> Double {
self.to
}
///|
pub fn Tween::duration(self : Tween) -> Double {
self.duration
}
///|
pub fn Tween::delay(self : Tween) -> Double {
self.delay
}
///|
pub fn Tween::repeat_count(self : Tween) -> Int {
self.repeat_count
}
///|
pub fn Tween::total_duration(self : Tween) -> Double {
self.delay + self.duration * (self.repeat_count + 1).to_double()
}
///|
fn Tween::value_at_progress(
self : Tween,
progress : Double,
reverse : Bool,
) -> Double {
let directed = if reverse { 1.0 - progress } else { progress }
let eased = self.curve.apply(directed)
self.from + (self.to - self.from) * eased
}
///|
fn Tween::sample_active(self : Tween, time : Double) -> TweenSample {
let elapsed = time - self.delay
let raw_cycle = @math.floor(elapsed / self.duration)
let mut cycle = raw_cycle.to_int()
if cycle < 0 {
cycle = 0
}
if cycle > self.repeat_count {
cycle = self.repeat_count
}
let cycle_start = cycle.to_double() * self.duration
let progress = clamp01((elapsed - cycle_start) / self.duration)
let reverse = self.mode is Mirror && cycle % 2 == 1
{
time,
value: self.value_at_progress(progress, reverse),
progress,
cycle,
state: Active,
}
}
///|
/// Sample a tween at wall-clock time. Time and duration use the same units.
pub fn Tween::sample(self : Tween, time : Double) -> TweenSample {
if time < self.delay {
{ time, value: self.from, progress: 0.0, cycle: 0, state: Before }
} else if time >= self.total_duration() {
let reverse = self.mode is Mirror && self.repeat_count % 2 == 1
{
time,
value: self.value_at_progress(1.0, reverse),
progress: 1.0,
cycle: self.repeat_count,
state: After,
}
} else {
self.sample_active(time)
}
}
///|
pub fn Tween::value_at(self : Tween, time : Double) -> Double {
self.sample(time).value
}
///|
pub fn Tween::sample_motion(self : Tween, time : Double) -> MotionSample {
let delta = (self.duration / 10000.0).max(0.000001)
let value = self.value_at(time)
let before = self.value_at(time - delta)
let after = self.value_at(time + delta)
{
time,
value,
velocity: (after - before) / (2.0 * delta),
acceleration: (after - 2.0 * value + before) / (delta * delta),
}
}
///|
pub fn Tween::sample_many(
self : Tween,
count : Int,
) -> Array[TweenSample] raise MotionError {
if count < 2 {
raise MotionError::InvalidSampleCount(count)
}
let result : Array[TweenSample] = []
let total = self.total_duration()
for i in 0..