Skip to main content

gpui_motion_kit/
playback.rs

1//! Kit-native clocks and rendering for the shared prepared playback engine.
2use crate::{Clock, Frame, core};
3use core::{Animation, AnimationError, Target};
4use gpui_kit::{App, Window};
5
6use std::{
7    ops::{Deref, DerefMut},
8    time::Duration,
9};
10
11/// Retained prepared-clip playback using Kit's controllable executor clock.
12/// Controls are inherited from `core::AnimationState`; use `now(cx)` for them.
13#[derive(Debug, Default)]
14pub struct AnimationState {
15    inner: core::AnimationState,
16    clock: Clock,
17}
18
19impl AnimationState {
20    /// Creates a player at a static initial target. Retain it between renders.
21    pub fn new(initial: Target) -> Self {
22        Self {
23            inner: core::AnimationState::new(initial),
24            clock: Clock::default(),
25        }
26    }
27
28    /// Current native timestamp for pause/play/seek/speed/restart controls.
29    /// Returns zero before the first native frame; no system clock is mixed in.
30    pub fn now(&self, cx: &App) -> Duration {
31        self.clock.now(cx)
32    }
33
34    /// Samples a prepared clip and schedules only while playing or delayed.
35    pub fn frame(&mut self, animation: &Animation, window: &mut Window, cx: &App) -> Frame {
36        let now = self.clock.tick(cx);
37
38        let frame = self.sample(animation, now, cx.reduce_motion());
39
40        if frame.is_animating() {
41            window.request_animation_frame();
42        }
43
44        frame
45    }
46
47    /// Samples an explicit epoch and returns a Kit-ready frame.
48    /// Do not mix explicit timestamps with native `frame`/`now` on one player.
49    pub fn sample(&mut self, animation: &Animation, now: Duration, reduced_motion: bool) -> Frame {
50        self.inner.sample(animation, now, reduced_motion).into()
51    }
52}
53
54impl Deref for AnimationState {
55    type Target = core::AnimationState;
56
57    fn deref(&self) -> &Self::Target {
58        &self.inner
59    }
60}
61
62impl DerefMut for AnimationState {
63    fn deref_mut(&mut self) -> &mut Self::Target {
64        &mut self.inner
65    }
66}
67
68/// Prepared tracks for separate Kit elements, using the shared timeline engine.
69/// Track insertion allocates; sampling reuses a Kit frame buffer. Retain the
70/// timeline, add tracks once, and use `now(cx)` for playback controls.
71#[derive(Debug, Default)]
72pub struct Timeline {
73    inner: core::Timeline,
74    frames: Vec<Frame>,
75    clock: Clock,
76}
77
78impl Timeline {
79    fn prepare_frame(&mut self, index: usize) -> usize {
80        debug_assert_eq!(self.frames.len(), index);
81        self.frames.push(core::Frame::at(Target::default()).into());
82
83        index
84    }
85
86    /// Adds an absolute track and returns its stable frame index.
87    pub fn at(&mut self, at: Duration, animation: Animation) -> usize {
88        let index = self.inner.at(at, animation);
89
90        self.prepare_frame(index)
91    }
92
93    /// Appends after the last finite track. Infinite predecessors return an error.
94    pub fn then(&mut self, animation: Animation) -> Result<usize, AnimationError> {
95        let index = self.inner.then(animation)?;
96
97        Ok(self.prepare_frame(index))
98    }
99
100    /// Records a unique label at the current finite sequence cursor.
101    pub fn label(&mut self, name: impl Into<String>) -> Result<(), AnimationError> {
102        self.inner.label(name)
103    }
104
105    /// Adds a track relative to an existing label, returning its frame index.
106    pub fn at_label(
107        &mut self,
108        name: &str,
109        offset: Duration,
110        animation: Animation,
111    ) -> Result<usize, AnimationError> {
112        let index = self.inner.at_label(name, offset, animation)?;
113
114        Ok(self.prepare_frame(index))
115    }
116
117    /// Current native timestamp. Returns zero before the first `frame` call.
118    pub fn now(&self, cx: &App) -> Duration {
119        self.clock.now(cx)
120    }
121
122    /// Starts all prepared tracks over using this owner's clock.
123    pub fn restart(&mut self, now: Duration) {
124        self.inner.restart(now);
125    }
126
127    /// Pauses all tracks, including delays.
128    pub fn pause(&mut self, now: Duration) {
129        self.inner.pause(now);
130    }
131
132    /// Resumes all tracks.
133    pub fn play(&mut self, now: Duration) {
134        self.inner.play(now);
135    }
136
137    /// Holds every track at an absolute playhead, including backward seeks.
138    pub fn seek(&mut self, playhead: Duration, now: Duration) {
139        self.inner.seek(playhead, now);
140    }
141
142    /// Sets a finite rate; negative values play backward.
143    pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
144        self.inner.set_speed(speed, now)
145    }
146
147    /// Samples one explicit epoch without allocating. Do not mix with native time.
148    pub fn sample(&mut self, now: Duration, reduced_motion: bool) -> &[Frame] {
149        for (output, input) in self
150            .frames
151            .iter_mut()
152            .zip(self.inner.sample(now, reduced_motion))
153        {
154            *output = (*input).into();
155        }
156
157        &self.frames
158    }
159
160    /// Samples native time and schedules even when only a future track is pending.
161    pub fn frame(&mut self, window: &mut Window, cx: &App) -> &[Frame] {
162        let now = self.clock.tick(cx);
163
164        self.sample(now, cx.reduce_motion());
165
166        if self.inner.is_animating() {
167            window.request_animation_frame();
168        }
169
170        &self.frames
171    }
172
173    /// Whether the last sample needs another frame (including future tracks).
174    pub fn is_animating(&self) -> bool {
175        self.inner.is_animating()
176    }
177}