gpui_kit/motion/
sequence.rs1use std::time::Duration;
4
5use super::MotionSpec;
6
7#[derive(Debug, Clone, Default, PartialEq)]
21pub struct Sequence {
22 steps: Vec<MotionSpec>,
24}
25
26impl Sequence {
27 pub fn new(steps: impl IntoIterator<Item = MotionSpec>) -> Self {
28 Self {
29 steps: steps.into_iter().collect(),
30 }
31 }
32
33 pub fn then(mut self, spec: MotionSpec) -> Self {
36 self.steps.push(spec);
37 self
38 }
39
40 pub fn len(&self) -> usize {
41 self.steps.len()
42 }
43
44 pub fn is_empty(&self) -> bool {
45 self.steps.is_empty()
46 }
47
48 pub fn start(&self, index: usize) -> Duration {
51 self.steps
52 .iter()
53 .take(index.min(self.steps.len()))
54 .map(|spec| spec.total())
55 .sum()
56 }
57
58 pub fn step(&self, index: usize) -> Option<MotionSpec> {
61 let spec = *self.steps.get(index)?;
62 Some(spec.with_delay(spec.delay_ms + self.start(index).as_millis() as u64))
63 }
64
65 pub fn steps(&self) -> impl Iterator<Item = MotionSpec> + '_ {
66 (0..self.steps.len()).filter_map(|index| self.step(index))
67 }
68
69 pub fn total(&self) -> Duration {
71 self.steps.iter().map(|spec| spec.total()).sum()
72 }
73
74 pub fn progress(&self, index: usize, raw: f32) -> f32 {
81 let Some(spec) = self.steps.get(index).copied() else {
82 return 1.0;
83 };
84 let elapsed = self.total().mul_f32(raw.clamp(0.0, 1.0));
85 let local = elapsed.saturating_sub(self.start(index));
86 let span = spec.total();
87 if span.is_zero() {
88 return 1.0;
89 }
90 spec.progress((local.as_secs_f32() / span.as_secs_f32()).min(1.0))
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::motion::CubicBezier;
98
99 fn linear(ms: u64) -> MotionSpec {
100 MotionSpec::new(ms, CubicBezier::new(0.0, 0.0, 1.0, 1.0))
101 }
102
103 fn sequence() -> Sequence {
104 Sequence::new([linear(200)]).then(linear(100).with_delay(50))
105 }
106
107 #[test]
108 fn a_step_starts_when_the_one_before_it_ends() {
109 let sequence = sequence();
110 assert_eq!(sequence.start(0), Duration::ZERO);
111 assert_eq!(sequence.start(1), Duration::from_millis(200));
112 assert_eq!(sequence.step(1).expect("two steps").delay_ms, 250);
113 }
114
115 #[test]
116 fn the_total_is_the_sum_of_the_steps() {
117 assert_eq!(sequence().total(), Duration::from_millis(350));
118 assert_eq!(Sequence::default().total(), Duration::ZERO);
119 }
120
121 #[test]
122 fn an_empty_sequence_has_no_steps_to_report() {
123 let empty = Sequence::default();
124 assert!(empty.is_empty());
125 assert_eq!(empty.len(), 0);
126 assert!(empty.step(0).is_none());
127 }
128
129 #[test]
130 fn a_step_holds_at_both_ends_of_its_own_span() {
131 let sequence = sequence();
132 assert_eq!(sequence.progress(1, 0.5), 0.0);
134 assert_eq!(sequence.progress(0, 1.0), 1.0);
136 assert!((sequence.progress(0, 200.0 / 350.0) - 1.0).abs() < 0.01);
137 }
138
139 #[test]
140 fn the_steps_carry_the_offsets_the_sequence_gave_them() {
141 let delays: Vec<u64> = sequence().steps().map(|spec| spec.delay_ms).collect();
142 assert_eq!(delays, vec![0, 250]);
143 }
144}