use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Transition {
start: Instant,
duration: Duration,
}
impl Transition {
pub fn start(duration: Duration) -> Self {
Self::starting_at(Instant::now(), duration)
}
pub fn starting_at(start: Instant, duration: Duration) -> Self {
Self { start, duration }
}
pub fn start_time(&self) -> Instant {
self.start
}
pub fn duration(&self) -> Duration {
self.duration
}
pub fn progress_at(&self, now: Instant) -> f32 {
if self.duration.is_zero() {
return 1.0;
}
if now <= self.start {
return 0.0;
}
let elapsed = now.duration_since(self.start);
if elapsed >= self.duration {
return 1.0;
}
elapsed.as_secs_f32() / self.duration.as_secs_f32()
}
pub fn progress(&self) -> f32 {
self.progress_at(Instant::now())
}
pub fn is_finished_at(&self, now: Instant) -> bool {
self.progress_at(now) >= 1.0
}
pub fn is_finished(&self) -> bool {
self.is_finished_at(Instant::now())
}
}
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}
pub fn lerp_rect(a: crate::event::Rect, b: crate::event::Rect, t: f32) -> crate::event::Rect {
crate::event::Rect::new(
lerp(a.x, b.x, t),
lerp(a.y, b.y, t),
lerp(a.width, b.width, t),
lerp(a.height, b.height, t),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_duration_is_immediately_finished() {
let t = Transition::starting_at(Instant::now(), Duration::ZERO);
assert_eq!(t.progress_at(Instant::now()), 1.0);
assert!(t.is_finished_at(Instant::now()));
}
#[test]
fn progress_clamps_to_0_at_start() {
let start = Instant::now();
let t = Transition::starting_at(start, Duration::from_secs(1));
assert_eq!(t.progress_at(start), 0.0);
assert!(!t.is_finished_at(start));
}
#[test]
fn progress_is_linear_mid_transition() {
let start = Instant::now();
let t = Transition::starting_at(start, Duration::from_secs(4));
let mid = start + Duration::from_secs(1);
assert!((t.progress_at(mid) - 0.25).abs() < 1e-6);
}
#[test]
fn progress_clamps_to_1_after_duration() {
let start = Instant::now();
let t = Transition::starting_at(start, Duration::from_secs(1));
let after = start + Duration::from_secs(5);
assert_eq!(t.progress_at(after), 1.0);
assert!(t.is_finished_at(after));
}
#[test]
fn progress_clamps_to_0_before_start() {
let start = Instant::now() + Duration::from_secs(1);
let t = Transition::starting_at(start, Duration::from_secs(1));
assert_eq!(t.progress_at(Instant::now()), 0.0);
}
#[test]
fn lerp_interpolates() {
assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
assert_eq!(lerp(0.0, 10.0, 0.5), 5.0);
}
#[test]
fn lerp_rect_interpolates_each_field() {
let a = crate::event::Rect::new(0.0, 0.0, 10.0, 10.0);
let b = crate::event::Rect::new(10.0, 20.0, 30.0, 40.0);
let mid = lerp_rect(a, b, 0.5);
assert_eq!(mid, crate::event::Rect::new(5.0, 10.0, 20.0, 25.0));
}
#[test]
fn accessors_report_start_and_duration() {
let start = Instant::now();
let dur = Duration::from_millis(250);
let t = Transition::starting_at(start, dur);
assert_eq!(t.start_time(), start);
assert_eq!(t.duration(), dur);
}
}