Skip to main content

gpui_ce_motion/
animation.rs

1//! Prepared clips, keyframes, playback clocks, and explicit timeline orchestration.
2use crate::{Easing, Frame, Spring, Target, Transition, value::sample_transition};
3#[cfg(feature = "gpui")]
4use std::time::Instant;
5use std::{error::Error, fmt, sync::Arc, time::Duration};
6
7/// Invalid prepared animation, timing, or orchestration input.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum AnimationError {
11    /// At least two keyframes are required, at zero and one.
12    Keyframes,
13    /// Times must be finite, strictly increasing, and in zero through one.
14    Times,
15    /// A repeat requires positive cycle duration.
16    ZeroPeriod,
17    /// Speed must be finite.
18    Speed,
19    /// A label is duplicated or missing.
20    Label,
21    /// An infinite clip cannot define a finite sequence cursor.
22    InfiniteSequence,
23    /// A stagger index must belong to a nonempty collection.
24    Index,
25}
26
27impl fmt::Display for AnimationError {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "invalid animation input: {self:?}")
30    }
31}
32
33impl Error for AnimationError {}
34
35/// One timed target. A missing target holds the previous value, or current presentation at zero.
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub struct Keyframe {
38    at: f64,
39    target: Option<Target>,
40    easing: Easing,
41}
42
43impl Keyframe {
44    /// A complete target at normalized progress `at`. Validated when preparing the clip.
45    pub fn new(at: f64, target: Target) -> Self {
46        Self {
47            at,
48            target: Some(target),
49            easing: Easing::default(),
50        }
51    }
52
53    /// A wildcard: current presentation at zero, otherwise hold the preceding target.
54    pub fn current(at: f64) -> Self {
55        Self {
56            at,
57            target: None,
58            easing: Easing::default(),
59        }
60    }
61
62    /// Easing for the segment ending at this keyframe.
63    pub fn easing(mut self, easing: Easing) -> Self {
64        self.easing = easing;
65
66        self
67    }
68}
69
70/// Additional cycles after the first cycle.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub enum Repeat {
73    /// A finite number of additional cycles. Zero plays once.
74    Count(u32),
75    /// Repeat until stopped or reduced motion snaps the animation.
76    Forever,
77}
78
79impl Default for Repeat {
80    fn default() -> Self {
81        Self::Count(0)
82    }
83}
84
85/// How repeated clips travel between their endpoints.
86#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
87pub enum RepeatMode {
88    /// Restart forward from the original beginning.
89    #[default]
90    Loop,
91    /// Play the same trajectory backwards on alternate cycles.
92    Reverse,
93    /// Reverse targets on alternate cycles, retaining forward easing/physics.
94    Mirror,
95}
96
97#[derive(Clone, Debug, PartialEq)]
98struct Clip {
99    frames: Vec<Keyframe>,
100    duration: Duration,
101    spring: Option<Spring>,
102}
103
104/// A prepared, cheaply cloned clip. Construct once outside rendering; clones share keyframes.
105#[derive(Clone, Debug)]
106pub struct Animation {
107    clip: Arc<Clip>,
108    delay: Duration,
109    repeat: Repeat,
110    mode: RepeatMode,
111    repeat_delay: Duration,
112}
113
114impl Animation {
115    /// Prepares keyframes with normalized, strictly increasing times from zero to one.
116    pub fn keyframes(
117        duration: Duration,
118        frames: impl IntoIterator<Item = Keyframe>,
119    ) -> Result<Self, AnimationError> {
120        let frames: Vec<_> = frames.into_iter().collect();
121
122        if frames.len() < 2 || frames.first().unwrap().at != 0.0 || frames.last().unwrap().at != 1.0
123        {
124            return Err(AnimationError::Keyframes);
125        }
126
127        if frames
128            .iter()
129            .any(|f| !f.at.is_finite() || !(0.0..=1.0).contains(&f.at))
130            || frames.windows(2).any(|p| p[0].at >= p[1].at)
131        {
132            return Err(AnimationError::Times);
133        }
134
135        Ok(Self {
136            clip: Arc::new(Clip {
137                frames,
138                duration,
139                spring: None,
140            }),
141            delay: Duration::ZERO,
142            repeat: Repeat::default(),
143            mode: RepeatMode::Loop,
144            repeat_delay: Duration::ZERO,
145        })
146    }
147
148    /// A tween from the current presentation, with explicit duration and easing.
149    pub fn to(target: Target, duration: Duration, easing: Easing) -> Self {
150        Self::keyframes(
151            duration,
152            [
153                Keyframe::current(0.0),
154                Keyframe::new(1.0, target).easing(easing),
155            ],
156        )
157        .expect("valid two-frame clip")
158    }
159
160    /// A finite physical spring clip from current position and velocity.
161    /// `duration` is an explicit deadline: the endpoint snaps exactly at that time.
162    /// Use ordinary `Motion` springs for unconstrained, tolerance-based settling.
163    pub fn spring(target: Target, spring: Spring, duration: Duration) -> Self {
164        let mut animation = Self::to(target, duration, Easing::default());
165
166        Arc::make_mut(&mut animation.clip).spring = Some(spring);
167
168        animation
169    }
170
171    /// Holds the first presentation before playback starts.
172    pub fn delay(mut self, duration: Duration) -> Self {
173        self.delay = duration;
174
175        self
176    }
177
178    /// Sets repeat count/mode and the hold between cycles.
179    pub fn repeat(
180        mut self,
181        repeat: Repeat,
182        mode: RepeatMode,
183        delay: Duration,
184    ) -> Result<Self, AnimationError> {
185        if self.clip.duration.is_zero() && repeat != Repeat::Count(0) {
186            return Err(AnimationError::ZeroPeriod);
187        }
188
189        self.repeat = repeat;
190        self.mode = mode;
191        self.repeat_delay = delay;
192
193        Ok(self)
194    }
195
196    /// Total duration, including initial/inter-cycle holds; `None` denotes infinity.
197    pub fn duration(&self) -> Option<Duration> {
198        match self.repeat {
199            Repeat::Forever => None,
200            Repeat::Count(count) => Some(
201                self.delay
202                    .saturating_add(
203                        self.clip
204                            .duration
205                            .saturating_mul(count)
206                            .saturating_add(self.clip.duration),
207                    )
208                    .saturating_add(self.repeat_delay.saturating_mul(count)),
209            ),
210        }
211    }
212
213    fn same(&self, other: &Self) -> bool {
214        Arc::ptr_eq(&self.clip, &other.clip)
215            && self.delay == other.delay
216            && self.repeat == other.repeat
217            && self.mode == other.mode
218            && self.repeat_delay == other.repeat_delay
219    }
220
221    fn resolve(&self, time: Duration, origin: Frame, force_end: bool) -> (Frame, u64, bool) {
222        let duration = self.clip.duration.as_secs_f64();
223        let period = duration + self.repeat_delay.as_secs_f64();
224        let relative = time.saturating_sub(self.delay).as_secs_f64();
225        let done = force_end || self.duration().is_some_and(|end| time >= end);
226
227        let iteration = if done {
228            match self.repeat {
229                Repeat::Count(n) => u64::from(n),
230                Repeat::Forever => 0,
231            }
232        } else if period > 0.0 {
233            (relative / period).floor().min(u64::MAX as f64) as u64
234        } else {
235            0
236        };
237
238        let local = if done {
239            duration
240        } else {
241            (relative - iteration as f64 * period).clamp(0.0, duration)
242        };
243
244        let mut progress = if duration > 0.0 {
245            local / duration
246        } else {
247            1.0
248        };
249
250        if time < self.delay && !done {
251            progress = 0.0;
252        }
253
254        let odd = !iteration.is_multiple_of(2);
255        let reverse = odd && self.mode == RepeatMode::Reverse;
256
257        if reverse {
258            progress = 1.0 - progress;
259        }
260
261        let mut from = origin.target().inherit(
262            self.clip
263                .frames
264                .last()
265                .and_then(|f| f.target)
266                .unwrap_or_default(),
267        );
268
269        from = self.clip.frames[0].target.unwrap_or(from).inherit(from);
270        let first = from;
271
272        let final_target = self.clip.frames.iter().fold(from, |previous, frame| {
273            frame.target.unwrap_or(previous).inherit(previous)
274        });
275
276        if let Some(spring) = self.clip.spring {
277            let (start, end) = if odd && self.mode == RepeatMode::Mirror {
278                (final_target, first)
279            } else {
280                (first, final_target)
281            };
282
283            let velocity = if iteration == 0 {
284                origin_velocities(origin)
285            } else {
286                [0.0; 6]
287            };
288
289            let mut values = [0.0; 6];
290            let mut velocities = [0.0; 6];
291
292            for ix in 0..6 {
293                let (v, speed, _) = sample_transition(
294                    start.values()[ix],
295                    velocity[ix],
296                    end.values()[ix],
297                    Transition::Spring(spring),
298                    progress * duration,
299                );
300
301                values[ix] = if progress == 0.0 {
302                    start.values()[ix]
303                } else if progress == 1.0 {
304                    end.values()[ix]
305                } else {
306                    v
307                };
308                velocities[ix] = if progress == 1.0 || done || time < self.delay {
309                    0.0
310                } else if reverse {
311                    -speed
312                } else {
313                    speed
314                };
315            }
316
317            return (
318                Frame::animated(
319                    Target::from_values(values, start.mask() | end.mask() | 7),
320                    velocities,
321                    !done,
322                    false,
323                ),
324                iteration,
325                done,
326            );
327        }
328
329        let mirrored = odd && self.mode == RepeatMode::Mirror;
330        let sample_progress = if mirrored { 1.0 - progress } else { progress };
331
332        let upper = self
333            .clip
334            .frames
335            .partition_point(|frame| frame.at < sample_progress)
336            .clamp(1, self.clip.frames.len() - 1);
337
338        let mut a = first;
339
340        for frame in self.clip.frames.iter().take(upper).skip(1) {
341            a = frame.target.unwrap_or(a).inherit(a);
342        }
343
344        let mut b = self.clip.frames[upper].target.unwrap_or(a).inherit(a);
345        let lo = self.clip.frames[upper - 1].at;
346        let hi = self.clip.frames[upper].at;
347        let mut u = (sample_progress - lo) / (hi - lo);
348
349        if mirrored {
350            std::mem::swap(&mut a, &mut b);
351            u = 1.0 - u;
352        }
353
354        let span = (hi - lo) * duration;
355        let easing = self.clip.frames[upper].easing;
356        let (weight, derivative) = easing.sample(u.clamp(0.0, 1.0));
357        let av = a.values();
358        let bv = b.values();
359
360        let values = std::array::from_fn(|ix| {
361            if u <= 0.0 {
362                av[ix]
363            } else if u >= 1.0 {
364                bv[ix]
365            } else {
366                crate::value::finite(av[ix] + (bv[ix] - av[ix]) * weight)
367            }
368        });
369
370        let velocity = std::array::from_fn(|ix| {
371            if done || time < self.delay || local >= duration || span == 0.0 {
372                0.0
373            } else {
374                crate::value::finite(
375                    (bv[ix] - av[ix]) * derivative / span * if reverse { -1.0 } else { 1.0 },
376                )
377            }
378        });
379
380        (
381            Frame::animated(
382                Target::from_values(values, a.mask() | b.mask() | 7),
383                velocity,
384                !done,
385                false,
386            ),
387            iteration,
388            done,
389        )
390    }
391}
392
393fn origin_velocities(frame: Frame) -> [f64; 6] {
394    let basic = frame.velocity();
395    let layout = frame.layout_velocity();
396
397    [
398        basic[0],
399        basic[1],
400        basic[2],
401        layout[0].unwrap_or(0.0),
402        layout[1].unwrap_or(0.0),
403        layout[2].unwrap_or(0.0),
404    ]
405}
406
407/// An explicit monotonic playback clock. It performs no scheduling.
408#[derive(Clone, Debug)]
409pub struct Playback {
410    anchor: Duration,
411    offset: f64,
412    speed: f64,
413    paused: bool,
414    last: Duration,
415}
416
417impl Default for Playback {
418    fn default() -> Self {
419        Self {
420            anchor: Duration::ZERO,
421            offset: 0.0,
422            speed: 1.0,
423            paused: false,
424            last: Duration::ZERO,
425        }
426    }
427}
428
429impl Playback {
430    /// Virtual playhead at a monotonic real timestamp. Backward real timestamps are held.
431    pub fn time(&mut self, now: Duration) -> Duration {
432        let now = now.max(self.last);
433
434        self.last = now;
435        let seconds = self.offset
436            + if self.paused {
437                0.0
438            } else {
439                now.saturating_sub(self.anchor).as_secs_f64() * self.speed
440            };
441
442        Duration::try_from_secs_f64(seconds.max(0.0)).unwrap_or(Duration::MAX)
443    }
444
445    /// Freezes at the current playhead.
446    pub fn pause(&mut self, now: Duration) {
447        self.offset = self.time(now).as_secs_f64();
448        self.anchor = now.max(self.last).max(self.anchor);
449        self.paused = true;
450    }
451
452    /// Resumes from the current playhead.
453    pub fn play(&mut self, now: Duration) {
454        if self.paused {
455            self.anchor = now.max(self.last).max(self.anchor);
456            self.paused = false;
457        }
458    }
459
460    /// Sets the playhead without changing speed or paused state.
461    pub fn seek(&mut self, time: Duration, now: Duration) {
462        self.last = self.last.max(now);
463        self.offset = time.as_secs_f64();
464        self.anchor = now.max(self.last).max(self.anchor);
465    }
466
467    /// Sets finite playback speed. Zero holds; negative speed plays toward zero.
468    pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
469        if !speed.is_finite() {
470            return Err(AnimationError::Speed);
471        }
472
473        self.offset = self.time(now).as_secs_f64();
474        self.anchor = now.max(self.last).max(self.anchor);
475        self.speed = speed;
476
477        Ok(())
478    }
479
480    /// Current signed playback multiplier.
481    pub fn speed(&self) -> f64 {
482        self.speed
483    }
484
485    /// Whether this clock advances.
486    pub fn is_playing(&self) -> bool {
487        !self.paused && self.speed != 0.0
488    }
489}
490
491/// Retained playback for a prepared clip. New clips interrupt from current presentation.
492#[derive(Debug)]
493pub struct AnimationState {
494    clip: Option<Animation>,
495    clock: Playback,
496    origin: Frame,
497    current: Frame,
498    complete: bool,
499    force_end: bool,
500    iteration: u64,
501    observers: crate::completion::CompletionSource,
502    #[cfg(feature = "gpui")]
503    pub(crate) epoch: Instant,
504}
505
506impl Default for AnimationState {
507    fn default() -> Self {
508        Self::new(Target::default())
509    }
510}
511
512impl AnimationState {
513    /// Seeds the presentation before the first clip. No work or timers are started.
514    pub fn new(initial: Target) -> Self {
515        Self {
516            clip: None,
517            clock: Playback::default(),
518            origin: Frame::at(initial),
519            current: Frame::at(initial),
520            complete: false,
521            force_end: false,
522            iteration: 0,
523            observers: Default::default(),
524            #[cfg(feature = "gpui")]
525            epoch: Instant::now(),
526        }
527    }
528
529    /// Samples a retained prepared clip; cloning an `Animation` preserves its identity.
530    /// Rebuilding a clip intentionally restarts it. Reduced motion finishes even infinite clips.
531    pub fn sample(&mut self, animation: &Animation, now: Duration, reduced_motion: bool) -> Frame {
532        if !self.clip.as_ref().is_some_and(|old| old.same(animation)) {
533            if let Some(old) = &self.clip {
534                self.observers.resolve(crate::CompletionStatus::Cancelled);
535                let frame = old
536                    .resolve(self.clock.time(now), self.origin, self.force_end)
537                    .0;
538
539                let factor = if self.clock.is_playing() {
540                    self.clock.speed
541                } else {
542                    0.0
543                };
544
545                self.current = Frame::animated(
546                    frame.target(),
547                    origin_velocities(frame).map(|v| v * factor),
548                    frame.is_animating(),
549                    false,
550                );
551            }
552
553            self.origin = self.current;
554            let first = self.clip.is_none();
555
556            self.clip = Some(animation.clone());
557
558            if first {
559                self.clock.anchor = now.max(self.clock.last);
560                self.clock.last = self.clock.anchor;
561            } else {
562                self.clock.seek(Duration::ZERO, now);
563            }
564
565            self.complete = false;
566
567            if !first {
568                self.force_end = false;
569            }
570        }
571
572        self.force_end |= reduced_motion;
573        let time = self.clock.time(now);
574        let (frame, iteration, finished) = animation.resolve(time, self.origin, self.force_end);
575        let backward_end = self.clock.speed < 0.0 && time.is_zero();
576        let complete = finished || backward_end;
577
578        self.current = Frame::animated(
579            frame.target(),
580            origin_velocities(frame).map(|v| {
581                v * if self.clock.is_playing() {
582                    self.clock.speed
583                } else {
584                    0.0
585                }
586            }),
587            !complete && self.clock.is_playing(),
588            complete && !self.complete,
589        );
590
591        if complete && !self.complete {
592            self.observers.resolve(crate::CompletionStatus::Completed);
593        }
594
595        self.complete = complete;
596        self.iteration = iteration;
597
598        self.current
599    }
600
601    /// Pauses playback; call the host's notify method after controls change.
602    pub fn pause(&mut self, now: Duration) {
603        self.clock.pause(now);
604    }
605
606    /// Resumes playback.
607    pub fn play(&mut self, now: Duration) {
608        self.clock.play(now);
609    }
610
611    /// Stops at the current presentation. Use play to resume, or restart to reset.
612    pub fn stop(&mut self, now: Duration) {
613        self.pause(now);
614    }
615
616    /// Seeks to a virtual playhead, including initial delay and repeats.
617    pub fn seek(&mut self, time: Duration, now: Duration) {
618        self.clock.seek(time, now);
619    }
620
621    /// Changes playback speed; negative speed is supported after seeking forward.
622    pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
623        self.clock.set_speed(speed, now)
624    }
625
626    /// Restarts the same clip from its captured origin.
627    pub fn restart(&mut self, now: Duration) {
628        self.observers.resolve(crate::CompletionStatus::Cancelled);
629        self.clock.seek(Duration::ZERO, now);
630        self.clock.play(now);
631        self.force_end = false;
632        self.complete = false;
633    }
634
635    /// Finishes immediately on the next sample, including an infinite clip.
636    pub fn finish(&mut self) {
637        self.force_end = true;
638    }
639
640    /// A completion future for this run. Sampling drives it; replacement,
641    /// restart, explicit cancellation and owner drop resolve `Cancelled`.
642    pub fn completion(&mut self) -> crate::Completion {
643        self.observers.subscribe(self.complete)
644    }
645
646    /// Pauses playback and cancels completion observers. `play` may resume the
647    /// retained trajectory; a new observer can follow that resumed run.
648    pub fn cancel(&mut self, now: Duration) {
649        self.pause(now);
650        self.observers.resolve(crate::CompletionStatus::Cancelled);
651    }
652
653    pub(crate) fn current_velocity(&self) -> f64 {
654        self.current.velocity()[0]
655    }
656
657    /// Current repeat iteration, zero based.
658    pub fn iteration(&self) -> u64 {
659        self.iteration
660    }
661
662    /// Whether the last sample reached its endpoint.
663    pub fn is_complete(&self) -> bool {
664        self.complete
665    }
666
667    /// Whether controls permit time to advance.
668    pub fn is_playing(&self) -> bool {
669        self.clock.is_playing()
670    }
671}
672
673/// Origin of a staggered group.
674#[derive(Clone, Copy, Debug, PartialEq, Eq)]
675pub enum StaggerFrom {
676    /// First child starts first.
677    Start,
678    /// Last child starts first.
679    End,
680    /// Center children start first.
681    Center,
682}
683
684/// Computes a child's delay without allocating. Center origins support half-step offsets.
685pub fn stagger(
686    step: Duration,
687    ix: usize,
688    count: usize,
689    from: StaggerFrom,
690) -> Result<Duration, AnimationError> {
691    if count == 0 || ix >= count {
692        return Err(AnimationError::Index);
693    }
694
695    let distance = match from {
696        StaggerFrom::Start => ix as f64,
697        StaggerFrom::End => (count - 1 - ix) as f64,
698        StaggerFrom::Center => (ix as f64 - (count - 1) as f64 / 2.0).abs(),
699    };
700
701    Ok(Duration::try_from_secs_f64(step.as_secs_f64() * distance).unwrap_or(Duration::MAX))
702}
703
704#[derive(Debug)]
705struct Track {
706    at: Duration,
707    animation: Animation,
708    state: AnimationState,
709}
710
711/// A retained, allocation-free-at-sample timeline. Tracks target separate caller-owned elements.
712#[derive(Debug, Default)]
713pub struct Timeline {
714    tracks: Vec<Track>,
715    frames: Vec<Frame>,
716    clock: Playback,
717    cursor: Option<Duration>,
718    labels: Vec<(String, Duration)>,
719    active: bool,
720}
721
722impl Timeline {
723    /// Adds an absolute-positioned track. Returns its stable index.
724    pub fn at(&mut self, at: Duration, animation: Animation) -> usize {
725        let end = animation
726            .duration()
727            .map(|duration| at.saturating_add(duration));
728
729        self.cursor = end;
730        let mut state = AnimationState::default();
731
732        state.sample(&animation, Duration::ZERO, false);
733        self.frames.push(Frame::at(Target::default()));
734        self.tracks.push(Track {
735            at,
736            animation,
737            state,
738        });
739
740        self.tracks.len() - 1
741    }
742
743    /// Appends after the previously inserted track; an infinite predecessor is rejected.
744    pub fn then(&mut self, animation: Animation) -> Result<usize, AnimationError> {
745        let at = if self.tracks.is_empty() {
746            Duration::ZERO
747        } else {
748            self.cursor.ok_or(AnimationError::InfiniteSequence)?
749        };
750
751        Ok(self.at(at, animation))
752    }
753
754    /// Records a label at the current finite sequence cursor.
755    pub fn label(&mut self, name: impl Into<String>) -> Result<(), AnimationError> {
756        let name = name.into();
757
758        if self.labels.iter().any(|(n, _)| *n == name) {
759            return Err(AnimationError::Label);
760        }
761
762        let at = if self.tracks.is_empty() {
763            Duration::ZERO
764        } else {
765            self.cursor.ok_or(AnimationError::InfiniteSequence)?
766        };
767
768        self.labels.push((name, at));
769
770        Ok(())
771    }
772
773    /// Adds an overlapping track relative to a previously recorded label.
774    pub fn at_label(
775        &mut self,
776        name: &str,
777        offset: Duration,
778        animation: Animation,
779    ) -> Result<usize, AnimationError> {
780        let at = self
781            .labels
782            .iter()
783            .find(|(n, _)| n == name)
784            .ok_or(AnimationError::Label)?
785            .1;
786
787        Ok(self.at(at.saturating_add(offset), animation))
788    }
789
790    /// Starts this timeline's clock at zero at an application timestamp.
791    pub fn restart(&mut self, now: Duration) {
792        self.clock.seek(Duration::ZERO, now);
793        self.clock.play(now);
794
795        for track in &mut self.tracks {
796            track.state.restart(now);
797        }
798    }
799
800    /// Pauses the whole timeline.
801    pub fn pause(&mut self, now: Duration) {
802        self.clock.pause(now);
803    }
804
805    /// Resumes the whole timeline.
806    pub fn play(&mut self, now: Duration) {
807        self.clock.play(now);
808    }
809
810    /// Scrubs all tracks, including backward seeks.
811    pub fn seek(&mut self, time: Duration, now: Duration) {
812        self.clock.seek(time, now);
813    }
814
815    /// Changes the whole timeline playback rate, including negative playback.
816    pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
817        self.clock.set_speed(speed, now)
818    }
819
820    /// Samples all tracks into a retained buffer. No allocation occurs here.
821    pub fn sample(&mut self, now: Duration, reduced_motion: bool) -> &[Frame] {
822        let playhead = self.clock.time(now);
823        let playing = self.clock.is_playing() && !(self.clock.speed < 0.0 && playhead.is_zero());
824
825        self.active = false;
826
827        for (track, output) in self.tracks.iter_mut().zip(&mut self.frames) {
828            let local = playhead.saturating_sub(track.at);
829
830            track.state.seek(local, now);
831            let frame = track.state.sample(&track.animation, now, reduced_motion);
832            let pending = playhead < track.at && !reduced_motion;
833
834            self.active |= (pending || frame.is_animating()) && playing;
835            *output = Frame::animated(
836                frame.target(),
837                origin_velocities(frame).map(|v| {
838                    if pending || !playing {
839                        0.0
840                    } else {
841                        v * self.clock.speed
842                    }
843                }),
844                !pending && frame.is_animating() && playing,
845                !pending && frame.just_completed(),
846            );
847        }
848
849        &self.frames
850    }
851
852    /// Whether sampled tracks or future tracks require another frame.
853    pub fn is_animating(&self) -> bool {
854        self.active
855    }
856}
857
858impl PartialEq for Animation {
859    fn eq(&self, other: &Self) -> bool {
860        self.same(other)
861    }
862}