1use crate::{
4 MotionFrameDemand, MotionFrameReason, MotionModel, MotionRunState, MotionScalarSample,
5};
6use std::time::Duration;
7
8#[derive(Debug, Clone, PartialEq)]
14pub struct MotionSequence<K> {
15 steps: Vec<MotionSequenceStep<K>>,
16}
17
18impl<K> Default for MotionSequence<K> {
19 fn default() -> Self {
20 Self::new()
21 }
22}
23
24impl<K> MotionSequence<K> {
25 pub const fn new() -> Self {
27 Self { steps: Vec::new() }
28 }
29
30 pub fn steps(&self) -> &[MotionSequenceStep<K>] {
32 &self.steps
33 }
34
35 pub fn is_empty(&self) -> bool {
37 self.steps.is_empty()
38 }
39
40 pub fn len(&self) -> usize {
42 self.steps.len()
43 }
44
45 pub fn insert_at(&mut self, key: K, model: MotionModel, start_at: Duration) -> &mut Self {
47 self.steps
48 .push(MotionSequenceStep::new(key, model, start_at));
49 self
50 }
51
52 pub fn append(&mut self, key: K, model: MotionModel) -> &mut Self {
54 self.insert_at(key, model, self.duration_hint())
55 }
56
57 pub fn insert_with_previous(&mut self, key: K, model: MotionModel) -> &mut Self {
59 let start_at = self
60 .steps
61 .last()
62 .map(MotionSequenceStep::start_at)
63 .unwrap_or(Duration::ZERO);
64 self.insert_at(key, model, start_at)
65 }
66
67 pub fn insert_after_previous(
69 &mut self,
70 key: K,
71 model: MotionModel,
72 delay: Duration,
73 ) -> &mut Self {
74 let start_at = self
75 .steps
76 .last()
77 .map(MotionSequenceStep::end_hint)
78 .unwrap_or(Duration::ZERO);
79 self.insert_at(key, model, saturating_duration_add(start_at, delay))
80 }
81
82 pub fn insert_staggered(
84 &mut self,
85 keys: impl IntoIterator<Item = K>,
86 model: MotionModel,
87 start_at: Duration,
88 stagger: Duration,
89 ) -> &mut Self {
90 let mut next_start = start_at;
91 for key in keys {
92 self.insert_at(key, model, next_start);
93 next_start = saturating_duration_add(next_start, stagger);
94 }
95 self
96 }
97
98 pub fn duration_hint(&self) -> Duration {
100 self.steps
101 .iter()
102 .map(MotionSequenceStep::end_hint)
103 .max()
104 .unwrap_or(Duration::ZERO)
105 }
106}
107
108impl<K: Clone> MotionSequence<K> {
109 pub fn sample_at(&self, elapsed: Duration) -> MotionSequenceSample<K> {
111 let steps = self
112 .steps
113 .iter()
114 .map(|step| step.sample_at(elapsed))
115 .collect::<Vec<_>>();
116 let needs_frame = steps
117 .iter()
118 .any(|step| step.state().needs_frame_for_sequence());
119 let frame_demand = if needs_frame {
120 MotionFrameDemand::NeedsFrame(MotionFrameReason::UpdateRender)
121 } else {
122 MotionFrameDemand::Idle
123 };
124 MotionSequenceSample::new(steps, frame_demand)
125 }
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub struct MotionSequenceStep<K> {
131 key: K,
132 model: MotionModel,
133 start_at: Duration,
134 duration_hint: Duration,
135}
136
137impl<K> MotionSequenceStep<K> {
138 pub fn new(key: K, model: MotionModel, start_at: Duration) -> Self {
140 Self {
141 key,
142 model,
143 start_at,
144 duration_hint: model.sequence_duration_hint(),
145 }
146 }
147
148 pub const fn key(&self) -> &K {
150 &self.key
151 }
152
153 pub const fn model(&self) -> MotionModel {
155 self.model
156 }
157
158 pub const fn start_at(&self) -> Duration {
160 self.start_at
161 }
162
163 pub const fn duration_hint(&self) -> Duration {
165 self.duration_hint
166 }
167
168 pub fn end_hint(&self) -> Duration {
170 saturating_duration_add(self.start_at, self.duration_hint)
171 }
172}
173
174impl<K: Clone> MotionSequenceStep<K> {
175 pub fn sample_at(&self, elapsed: Duration) -> MotionSequenceStepSample<K> {
177 if elapsed < self.start_at {
178 return MotionSequenceStepSample::pending(self.key.clone());
179 }
180
181 let local_elapsed = elapsed.saturating_sub(self.start_at);
182 let sample = self
183 .model
184 .sample_scalar_elapsed(0.0, 1.0, 0.0, local_elapsed);
185 MotionSequenceStepSample::from_scalar(self.key.clone(), sample)
186 }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub enum MotionSequenceStepState {
192 Pending,
194 Active,
196 Immediate,
198 Completed,
200 Cancelled,
202}
203
204impl MotionSequenceStepState {
205 pub const fn needs_frame_for_sequence(self) -> bool {
207 matches!(self, Self::Pending | Self::Active)
208 }
209
210 pub const fn is_terminal(self) -> bool {
212 !self.needs_frame_for_sequence()
213 }
214
215 pub const fn reached_final_state(self) -> bool {
217 matches!(self, Self::Immediate | Self::Completed)
218 }
219
220 fn from_run_state(state: MotionRunState) -> Self {
221 match state {
222 MotionRunState::Immediate => Self::Immediate,
223 MotionRunState::Active => Self::Active,
224 MotionRunState::Completed => Self::Completed,
225 MotionRunState::Cancelled => Self::Cancelled,
226 }
227 }
228}
229
230#[derive(Debug, Clone, PartialEq)]
232pub struct MotionSequenceStepSample<K> {
233 key: K,
234 state: MotionSequenceStepState,
235 elapsed: Duration,
236 value: f32,
237 velocity: f32,
238 target: f32,
239}
240
241impl<K> MotionSequenceStepSample<K> {
242 fn pending(key: K) -> Self {
243 Self {
244 key,
245 state: MotionSequenceStepState::Pending,
246 elapsed: Duration::ZERO,
247 value: 0.0,
248 velocity: 0.0,
249 target: 1.0,
250 }
251 }
252
253 fn from_scalar(key: K, sample: MotionScalarSample) -> Self {
254 Self {
255 key,
256 state: MotionSequenceStepState::from_run_state(sample.state()),
257 elapsed: sample.elapsed(),
258 value: sample.value(),
259 velocity: sample.velocity(),
260 target: sample.target(),
261 }
262 }
263
264 pub const fn key(&self) -> &K {
266 &self.key
267 }
268
269 pub const fn state(&self) -> MotionSequenceStepState {
271 self.state
272 }
273
274 pub const fn elapsed(&self) -> Duration {
276 self.elapsed
277 }
278
279 pub const fn value(&self) -> f32 {
281 self.value
282 }
283
284 pub const fn velocity(&self) -> f32 {
286 self.velocity
287 }
288
289 pub const fn target(&self) -> f32 {
291 self.target
292 }
293
294 pub const fn needs_frame(&self) -> bool {
296 self.state.needs_frame_for_sequence()
297 }
298}
299
300#[derive(Debug, Clone, PartialEq)]
302pub struct MotionSequenceSample<K> {
303 steps: Vec<MotionSequenceStepSample<K>>,
304 frame_demand: MotionFrameDemand,
305}
306
307impl<K> MotionSequenceSample<K> {
308 pub fn new(steps: Vec<MotionSequenceStepSample<K>>, frame_demand: MotionFrameDemand) -> Self {
310 Self {
311 steps,
312 frame_demand,
313 }
314 }
315
316 pub fn steps(&self) -> &[MotionSequenceStepSample<K>] {
318 &self.steps
319 }
320
321 pub const fn frame_demand(&self) -> MotionFrameDemand {
323 self.frame_demand
324 }
325
326 pub const fn complete(&self) -> bool {
328 !self.frame_demand.needs_frame()
329 }
330}
331
332impl<K: PartialEq> MotionSequenceSample<K> {
333 pub fn step(&self, key: &K) -> Option<&MotionSequenceStepSample<K>> {
335 self.steps.iter().find(|step| step.key() == key)
336 }
337}
338
339fn saturating_duration_add(left: Duration, right: Duration) -> Duration {
340 left.checked_add(right).unwrap_or(Duration::MAX)
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346 use crate::{
347 MotionDuration, MotionEasing, MotionPreference, MotionSpec, MotionSpringPreset,
348 MotionSpringSpec,
349 };
350
351 fn linear_model(duration: Duration) -> MotionModel {
352 MotionModel::timeline(MotionSpec::new(
353 MotionPreference::Animated,
354 MotionDuration::Custom(duration),
355 MotionEasing::Linear,
356 ))
357 }
358
359 #[test]
360 fn sequence_positions_append_with_previous_and_after_previous() {
361 let model = linear_model(Duration::from_millis(100));
362 let mut sequence = MotionSequence::new();
363
364 sequence
365 .append("first", model)
366 .insert_with_previous("parallel", model)
367 .insert_after_previous("delayed", model, Duration::from_millis(20));
368
369 assert_eq!(sequence.steps()[0].start_at(), Duration::ZERO);
370 assert_eq!(sequence.steps()[1].start_at(), Duration::ZERO);
371 assert_eq!(sequence.steps()[2].start_at(), Duration::from_millis(120));
372 assert_eq!(sequence.duration_hint(), Duration::from_millis(220));
373 }
374
375 #[test]
376 fn staggered_steps_preserve_start_offsets() {
377 let model = linear_model(Duration::from_millis(50));
378 let mut sequence = MotionSequence::new();
379
380 sequence.insert_staggered(
381 ["a", "b", "c"],
382 model,
383 Duration::from_millis(10),
384 Duration::from_millis(20),
385 );
386
387 assert_eq!(sequence.steps()[0].start_at(), Duration::from_millis(10));
388 assert_eq!(sequence.steps()[1].start_at(), Duration::from_millis(30));
389 assert_eq!(sequence.steps()[2].start_at(), Duration::from_millis(50));
390 assert_eq!(sequence.duration_hint(), Duration::from_millis(100));
391 }
392
393 #[test]
394 fn sequence_samples_pending_active_and_completed_steps() {
395 let model = linear_model(Duration::from_millis(100));
396 let mut sequence = MotionSequence::new();
397 sequence.insert_at("row", model, Duration::from_millis(50));
398
399 let pending = sequence.sample_at(Duration::ZERO);
400 let pending_step = pending.step(&"row").expect("row step");
401 assert_eq!(pending_step.state(), MotionSequenceStepState::Pending);
402 assert!(pending.frame_demand().needs_frame());
403 assert!(!pending.complete());
404
405 let active = sequence.sample_at(Duration::from_millis(100));
406 let active_step = active.step(&"row").expect("row step");
407 assert_eq!(active_step.state(), MotionSequenceStepState::Active);
408 assert_eq!(active_step.elapsed(), Duration::from_millis(50));
409 assert_eq!(active_step.value(), 0.5);
410 assert!(active.frame_demand().needs_frame());
411
412 let complete = sequence.sample_at(Duration::from_millis(160));
413 let complete_step = complete.step(&"row").expect("row step");
414 assert_eq!(complete_step.state(), MotionSequenceStepState::Completed);
415 assert_eq!(complete_step.value(), 1.0);
416 assert!(!complete.frame_demand().needs_frame());
417 assert!(complete.complete());
418 }
419
420 #[test]
421 fn reduced_motion_step_completes_without_frame_demand_at_start() {
422 let model = MotionModel::timeline(MotionSpec::committed_layout(MotionPreference::Reduced));
423 let mut sequence = MotionSequence::new();
424 sequence.insert_at("panel", model, Duration::ZERO);
425
426 let sample = sequence.sample_at(Duration::ZERO);
427 let step = sample.step(&"panel").expect("panel step");
428
429 assert_eq!(step.state(), MotionSequenceStepState::Immediate);
430 assert_eq!(step.value(), 1.0);
431 assert!(step.state().reached_final_state());
432 assert!(sample.complete());
433 }
434
435 #[test]
436 fn spring_sequence_duration_hint_uses_review_duration() {
437 let spring = MotionSpringSpec::layout(MotionPreference::Animated);
438 let model = MotionModel::spring(spring);
439 let mut sequence = MotionSequence::new();
440
441 sequence.append("spring", model).append("next", model);
442
443 assert_eq!(
444 sequence.steps()[0].duration_hint(),
445 spring.physics().review_duration()
446 );
447 assert_eq!(
448 sequence.steps()[1].start_at(),
449 spring.physics().review_duration()
450 );
451 assert_eq!(spring.preset(), Some(MotionSpringPreset::Layout));
452 }
453}