use std::time::Duration;
use super::MotionSpec;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Sequence {
steps: Vec<MotionSpec>,
}
impl Sequence {
pub fn new(steps: impl IntoIterator<Item = MotionSpec>) -> Self {
Self {
steps: steps.into_iter().collect(),
}
}
pub fn then(mut self, spec: MotionSpec) -> Self {
self.steps.push(spec);
self
}
pub fn len(&self) -> usize {
self.steps.len()
}
pub fn is_empty(&self) -> bool {
self.steps.is_empty()
}
pub fn start(&self, index: usize) -> Duration {
self.steps
.iter()
.take(index.min(self.steps.len()))
.map(|spec| spec.total())
.sum()
}
pub fn step(&self, index: usize) -> Option<MotionSpec> {
let spec = *self.steps.get(index)?;
Some(spec.with_delay(spec.delay_ms + self.start(index).as_millis() as u64))
}
pub fn steps(&self) -> impl Iterator<Item = MotionSpec> + '_ {
(0..self.steps.len()).filter_map(|index| self.step(index))
}
pub fn total(&self) -> Duration {
self.steps.iter().map(|spec| spec.total()).sum()
}
pub fn progress(&self, index: usize, raw: f32) -> f32 {
let Some(spec) = self.steps.get(index).copied() else {
return 1.0;
};
let elapsed = self.total().mul_f32(raw.clamp(0.0, 1.0));
let local = elapsed.saturating_sub(self.start(index));
let span = spec.total();
if span.is_zero() {
return 1.0;
}
spec.progress((local.as_secs_f32() / span.as_secs_f32()).min(1.0))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::motion::CubicBezier;
fn linear(ms: u64) -> MotionSpec {
MotionSpec::new(ms, CubicBezier::new(0.0, 0.0, 1.0, 1.0))
}
fn sequence() -> Sequence {
Sequence::new([linear(200)]).then(linear(100).with_delay(50))
}
#[test]
fn a_step_starts_when_the_one_before_it_ends() {
let sequence = sequence();
assert_eq!(sequence.start(0), Duration::ZERO);
assert_eq!(sequence.start(1), Duration::from_millis(200));
assert_eq!(sequence.step(1).expect("two steps").delay_ms, 250);
}
#[test]
fn the_total_is_the_sum_of_the_steps() {
assert_eq!(sequence().total(), Duration::from_millis(350));
assert_eq!(Sequence::default().total(), Duration::ZERO);
}
#[test]
fn an_empty_sequence_has_no_steps_to_report() {
let empty = Sequence::default();
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
assert!(empty.step(0).is_none());
}
#[test]
fn a_step_holds_at_both_ends_of_its_own_span() {
let sequence = sequence();
assert_eq!(sequence.progress(1, 0.5), 0.0);
assert_eq!(sequence.progress(0, 1.0), 1.0);
assert!((sequence.progress(0, 200.0 / 350.0) - 1.0).abs() < 0.01);
}
#[test]
fn the_steps_carry_the_offsets_the_sequence_gave_them() {
let delays: Vec<u64> = sequence().steps().map(|spec| spec.delay_ms).collect();
assert_eq!(delays, vec![0, 250]);
}
}