1use crate::{
3 Animation, AnimationError, AnimationState, Easing, Keyframe, Playback, Repeat, RepeatMode,
4 Spring, Target,
5};
6
7use std::{
8 any::Any,
9 fmt::Debug,
10 marker::PhantomData,
11 sync::atomic::{AtomicU64, Ordering},
12 time::Duration,
13};
14
15pub trait MotionData<const N: usize>: Copy + Debug + PartialEq + 'static {
20 fn channels(self) -> [f64; N];
22
23 fn with_channels(self, values: [f64; N]) -> Self;
25
26 fn compatible(self, other: Self) -> bool;
28}
29
30impl MotionData<1> for f64 {
31 fn channels(self) -> [f64; 1] {
32 [self]
33 }
34
35 fn with_channels(self, values: [f64; 1]) -> Self {
36 values[0]
37 }
38
39 fn compatible(self, _: Self) -> bool {
40 true
41 }
42}
43
44impl MotionData<6> for Target {
45 fn channels(self) -> [f64; 6] {
46 self.values()
47 }
48
49 fn with_channels(self, values: [f64; 6]) -> Self {
50 Self::from_values(values, self.mask())
51 }
52
53 fn compatible(self, other: Self) -> bool {
54 self.mask() == other.mask()
55 }
56}
57
58#[derive(Clone, Copy, Debug)]
60pub struct TypedKeyframe<T> {
61 at: f64,
62 value: Option<T>,
63 easing: Easing,
64}
65
66impl<T> TypedKeyframe<T> {
67 pub fn new(at: f64, value: T) -> Self {
69 Self {
70 at,
71 value: Some(value),
72 easing: Easing::Linear,
73 }
74 }
75
76 pub fn current(at: f64) -> Self {
78 Self {
79 at,
80 value: None,
81 easing: Easing::Linear,
82 }
83 }
84
85 pub fn easing(mut self, easing: Easing) -> Self {
87 self.easing = easing;
88
89 self
90 }
91}
92
93fn scalar(value: f64) -> Target {
94 Target::from_values([value, 0.0, 1.0, 0.0, 0.0, 0.0], 1)
95}
96
97#[derive(Clone, Debug)]
100pub struct TypedAnimation<T, const N: usize> {
101 channels: [Animation; N],
102 template: T,
103}
104
105impl<T: MotionData<N>, const N: usize> TypedAnimation<T, N> {
106 pub fn keyframes(
109 duration: Duration,
110 frames: impl IntoIterator<Item = TypedKeyframe<T>>,
111 ) -> Result<Self, AnimationError> {
112 let frames: Vec<_> = frames.into_iter().collect();
113
114 let template = frames
115 .iter()
116 .find_map(|frame| frame.value)
117 .ok_or(AnimationError::Keyframes)?;
118
119 if N == 0
120 || frames.iter().filter_map(|frame| frame.value).any(|value| {
121 !template.compatible(value) || value.channels().iter().any(|v| !v.is_finite())
122 })
123 {
124 return Err(AnimationError::Keyframes);
125 }
126
127 let channels = (0..N)
128 .map(|i| {
129 Animation::keyframes(
130 duration,
131 frames.iter().map(|frame| {
132 let key = frame.value.map_or_else(
133 || Keyframe::current(frame.at),
134 |value| Keyframe::new(frame.at, scalar(value.channels()[i])),
135 );
136
137 key.easing(frame.easing)
138 }),
139 )
140 })
141 .collect::<Result<Vec<_>, _>>()?
142 .try_into()
143 .unwrap_or_else(|_| unreachable!());
144
145 Ok(Self { channels, template })
146 }
147
148 pub fn to(value: T, duration: Duration, easing: Easing) -> Result<Self, AnimationError> {
150 let values = value.channels();
151
152 if N == 0 || values.iter().any(|value| !value.is_finite()) {
153 return Err(AnimationError::Keyframes);
154 }
155
156 let channels = std::array::from_fn(|i| Animation::to(scalar(values[i]), duration, easing));
157
158 Ok(Self {
159 channels,
160 template: value,
161 })
162 }
163
164 pub fn spring(value: T, spring: Spring, deadline: Duration) -> Result<Self, AnimationError> {
166 if N == 0 || value.channels().iter().any(|v| !v.is_finite()) {
167 return Err(AnimationError::Keyframes);
168 }
169
170 let channels = std::array::from_fn(|i| {
171 Animation::spring(scalar(value.channels()[i]), spring, deadline)
172 });
173
174 Ok(Self {
175 channels,
176 template: value,
177 })
178 }
179
180 pub fn delay(mut self, duration: Duration) -> Self {
182 self.channels = self.channels.map(|channel| channel.delay(duration));
183
184 self
185 }
186
187 pub fn repeat(
189 mut self,
190 repeat: Repeat,
191 mode: RepeatMode,
192 delay: Duration,
193 ) -> Result<Self, AnimationError> {
194 self.channels = self
195 .channels
196 .into_iter()
197 .map(|channel| channel.repeat(repeat, mode, delay))
198 .collect::<Result<Vec<_>, _>>()?
199 .try_into()
200 .unwrap_or_else(|_| unreachable!());
201
202 Ok(self)
203 }
204
205 pub fn duration(&self) -> Option<Duration> {
207 self.channels[0].duration()
208 }
209}
210
211#[derive(Clone, Copy, Debug)]
213pub struct TypedFrame<T> {
214 value: T,
215 active: bool,
216 completed: bool,
217}
218
219impl<T: Copy> TypedFrame<T> {
220 pub fn value(self) -> T {
222 self.value
223 }
224
225 pub fn is_animating(self) -> bool {
227 self.active
228 }
229
230 pub fn just_completed(self) -> bool {
232 self.completed
233 }
234}
235
236#[derive(Debug)]
238pub struct TypedState<T, const N: usize> {
239 states: [AnimationState; N],
240 frame: TypedFrame<T>,
241 #[cfg(feature = "gpui")]
242 epoch: Option<std::time::Instant>,
243}
244
245impl<T: MotionData<N>, const N: usize> TypedState<T, N> {
246 pub fn new(value: T) -> Self {
248 assert!(
249 N > 0 && value.channels().iter().all(|v| v.is_finite()),
250 "invalid typed seed"
251 );
252
253 Self {
254 states: std::array::from_fn(|i| AnimationState::new(scalar(value.channels()[i]))),
255 frame: TypedFrame {
256 value,
257 active: false,
258 completed: false,
259 },
260 #[cfg(feature = "gpui")]
261 epoch: None,
262 }
263 }
264
265 pub fn sample(
268 &mut self,
269 clip: &TypedAnimation<T, N>,
270 now: Duration,
271 reduced: bool,
272 ) -> TypedFrame<T> {
273 assert!(
274 self.frame.value.compatible(clip.template),
275 "typed property masks must match"
276 );
277 let samples = std::array::from_fn::<_, N, _>(|i| {
278 self.states[i].sample(&clip.channels[i], now, reduced)
279 });
280
281 let active = samples.iter().any(|frame| frame.is_animating());
282
283 self.frame = TypedFrame {
284 value: clip
285 .template
286 .with_channels(samples.map(|frame| frame.target().values()[0])),
287 active,
288 completed: !active && samples.iter().any(|frame| frame.just_completed()),
289 };
290
291 self.frame
292 }
293
294 pub fn value(&self) -> T {
296 self.frame.value
297 }
298
299 pub fn velocities(&self) -> [f64; N] {
301 std::array::from_fn(|i| self.states[i].current_velocity())
302 }
303
304 pub fn pause(&mut self, now: Duration) {
306 for state in &mut self.states {
307 state.pause(now);
308 }
309 }
310
311 pub fn play(&mut self, now: Duration) {
313 for state in &mut self.states {
314 state.play(now);
315 }
316 }
317
318 pub fn seek(&mut self, time: Duration, now: Duration) {
320 for state in &mut self.states {
321 state.seek(time, now);
322 }
323 }
324
325 pub fn restart(&mut self, now: Duration) {
327 for state in &mut self.states {
328 state.restart(now);
329 }
330 }
331
332 pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
334 if !speed.is_finite() {
335 return Err(AnimationError::Speed);
336 }
337
338 for state in &mut self.states {
339 state.set_speed(speed, now)?;
340 }
341
342 Ok(())
343 }
344
345 pub fn finish(&mut self) {
347 for state in &mut self.states {
348 state.finish();
349 }
350 }
351
352 pub fn completion(&mut self) -> crate::Completion {
354 self.states[0].completion()
355 }
356
357 pub fn is_animating(&self) -> bool {
359 self.frame.active
360 }
361
362 #[cfg(feature = "gpui")]
364 pub fn now(&self, cx: &gpui::App) -> Duration {
365 self.epoch.map_or(Duration::ZERO, |epoch| {
366 cx.background_executor()
367 .now()
368 .saturating_duration_since(epoch)
369 })
370 }
371
372 #[cfg(feature = "gpui")]
374 pub fn frame(
375 &mut self,
376 clip: &TypedAnimation<T, N>,
377 window: &mut gpui::Window,
378 cx: &gpui::App,
379 ) -> TypedFrame<T> {
380 let instant = cx.background_executor().now();
381 let epoch = *self.epoch.get_or_insert(instant);
382
383 let frame = self.sample(
384 clip,
385 instant.saturating_duration_since(epoch),
386 cx.reduce_motion(),
387 );
388
389 if frame.active {
390 window.request_animation_frame();
391 }
392
393 frame
394 }
395}
396
397trait ErasedTrack: Debug {
398 fn sample(&mut self, time: Duration, now: Duration, speed: f64, reduced: bool) -> bool;
399
400 fn restart(&mut self);
401
402 fn as_any(&self) -> &dyn Any;
403}
404
405#[derive(Debug)]
406struct Track<T, const N: usize> {
407 clip: TypedAnimation<T, N>,
408 state: TypedState<T, N>,
409 start: Duration,
410}
411
412impl<T: MotionData<N>, const N: usize> ErasedTrack for Track<T, N> {
413 fn sample(&mut self, time: Duration, now: Duration, speed: f64, reduced: bool) -> bool {
414 self.state
415 .set_speed(speed, now)
416 .expect("validated timeline speed");
417 self.state.seek(time.saturating_sub(self.start), now);
418 self.state.sample(&self.clip, now, reduced);
419
420 (time < self.start && !reduced)
421 || !self.state.states.iter().all(|state| state.is_complete())
422 }
423
424 fn restart(&mut self) {
425 self.state.restart(Duration::ZERO);
426 }
427
428 fn as_any(&self) -> &dyn Any {
429 self
430 }
431}
432
433static NEXT_TIMELINE: AtomicU64 = AtomicU64::new(1);
434
435#[derive(Debug)]
437pub struct TrackId<T, const N: usize> {
438 owner: u64,
439 index: usize,
440 marker: PhantomData<T>,
441}
442
443impl<T, const N: usize> Copy for TrackId<T, N> {}
444
445impl<T, const N: usize> Clone for TrackId<T, N> {
446 fn clone(&self) -> Self {
447 *self
448 }
449}
450
451#[derive(Debug)]
455pub struct TypedTimeline {
456 owner: u64,
457 tracks: Vec<Box<dyn ErasedTrack>>,
458 clock: Playback,
459 cursor: Option<Duration>,
460 labels: Vec<(String, Duration)>,
461 active: bool,
462 completed: bool,
463 observers: crate::completion::CompletionSource,
464 #[cfg(feature = "gpui")]
465 epoch: Option<std::time::Instant>,
466}
467
468impl Default for TypedTimeline {
469 fn default() -> Self {
470 Self {
471 owner: NEXT_TIMELINE.fetch_add(1, Ordering::Relaxed),
472 tracks: Vec::new(),
473 clock: Playback::default(),
474 cursor: Some(Duration::ZERO),
475 labels: Vec::new(),
476 active: false,
477 completed: false,
478 observers: Default::default(),
479 #[cfg(feature = "gpui")]
480 epoch: None,
481 }
482 }
483}
484
485impl TypedTimeline {
486 pub fn at<T: MotionData<N>, const N: usize>(
488 &mut self,
489 start: Duration,
490 initial: T,
491 clip: TypedAnimation<T, N>,
492 ) -> TrackId<T, N> {
493 assert!(
494 initial.compatible(clip.template),
495 "typed property masks must match"
496 );
497 self.observers.resolve(crate::CompletionStatus::Cancelled);
498 self.cursor = clip
499 .duration()
500 .map(|duration| start.saturating_add(duration));
501 let id = TrackId {
502 owner: self.owner,
503 index: self.tracks.len(),
504 marker: PhantomData,
505 };
506
507 let mut state = TypedState::new(initial);
508
509 state.sample(&clip, Duration::ZERO, false);
510 self.tracks.push(Box::new(Track { state, clip, start }));
511 self.completed = false;
512
513 id
514 }
515
516 fn offset(base: Option<Duration>, seconds: f64) -> Result<Duration, AnimationError> {
517 let base = base.ok_or(AnimationError::Times)?;
518 let value = base.as_secs_f64() + seconds;
519
520 Duration::try_from_secs_f64(value).map_err(|_| AnimationError::Times)
521 }
522
523 pub fn after<T: MotionData<N>, const N: usize>(
526 &mut self,
527 seconds: f64,
528 initial: T,
529 clip: TypedAnimation<T, N>,
530 ) -> Result<TrackId<T, N>, AnimationError> {
531 let at = Self::offset(self.cursor, seconds)?;
532
533 Ok(self.at(at, initial, clip))
534 }
535
536 pub fn label(&mut self, name: impl Into<String>, at: Duration) -> Result<(), AnimationError> {
538 let name = name.into();
539
540 if self.labels.iter().any(|(old, _)| *old == name) {
541 return Err(AnimationError::Label);
542 }
543
544 self.labels.push((name, at));
545
546 Ok(())
547 }
548
549 pub fn at_label<T: MotionData<N>, const N: usize>(
551 &mut self,
552 name: &str,
553 seconds: f64,
554 initial: T,
555 clip: TypedAnimation<T, N>,
556 ) -> Result<TrackId<T, N>, AnimationError> {
557 let at = self
558 .labels
559 .iter()
560 .find(|(label, _)| label == name)
561 .map(|(_, at)| *at)
562 .ok_or(AnimationError::Label)?;
563
564 Ok(self.at(Self::offset(Some(at), seconds)?, initial, clip))
565 }
566
567 pub fn sample(&mut self, now: Duration, reduced: bool) {
569 let time = self.clock.time(now);
570 let mut unfinished = false;
571
572 let speed = if self.clock.is_playing() {
573 self.clock.speed()
574 } else {
575 0.0
576 };
577
578 for track in &mut self.tracks {
579 unfinished |= track.sample(time, now, speed, reduced);
580 }
581
582 if speed < 0.0 && time.is_zero() {
583 unfinished = false;
584 }
585
586 self.active = unfinished && self.clock.is_playing();
587
588 if !unfinished && !self.completed {
589 self.observers.resolve(crate::CompletionStatus::Completed);
590 }
591
592 self.completed = !unfinished;
593 }
594
595 pub fn value<T: MotionData<N>, const N: usize>(&self, id: TrackId<T, N>) -> Option<T> {
597 if id.owner != self.owner {
598 return None;
599 }
600
601 self.tracks
602 .get(id.index)?
603 .as_any()
604 .downcast_ref::<Track<T, N>>()
605 .map(|track| track.state.value())
606 }
607
608 pub fn pause(&mut self, now: Duration) {
610 self.clock.pause(now);
611 }
612
613 pub fn play(&mut self, now: Duration) {
615 self.clock.play(now);
616 }
617
618 pub fn seek(&mut self, time: Duration, now: Duration) {
620 self.clock.seek(time, now);
621 }
622
623 pub fn set_speed(&mut self, speed: f64, now: Duration) -> Result<(), AnimationError> {
625 self.clock.set_speed(speed, now)
626 }
627
628 pub fn restart(&mut self, now: Duration) {
630 self.observers.resolve(crate::CompletionStatus::Cancelled);
631 self.clock.seek(Duration::ZERO, now);
632 self.clock.play(now);
633
634 for track in &mut self.tracks {
635 track.restart();
636 }
637
638 self.completed = false;
639 }
640
641 pub fn completion(&mut self) -> crate::Completion {
643 self.observers.subscribe(self.completed)
644 }
645
646 pub fn is_animating(&self) -> bool {
648 self.active
649 }
650
651 #[cfg(feature = "gpui")]
653 pub fn now(&self, cx: &gpui::App) -> Duration {
654 self.epoch.map_or(Duration::ZERO, |epoch| {
655 cx.background_executor()
656 .now()
657 .saturating_duration_since(epoch)
658 })
659 }
660
661 #[cfg(feature = "gpui")]
663 pub fn frame(&mut self, window: &mut gpui::Window, cx: &gpui::App) {
664 let instant = cx.background_executor().now();
665 let epoch = *self.epoch.get_or_insert(instant);
666
667 self.sample(instant.saturating_duration_since(epoch), cx.reduce_motion());
668
669 if self.active {
670 window.request_animation_frame();
671 }
672 }
673}
674
675impl<const N: usize> MotionData<N> for [f64; N] {
676 fn channels(self) -> [f64; N] {
677 self
678 }
679
680 fn with_channels(self, values: [f64; N]) -> Self {
681 values
682 }
683
684 fn compatible(self, _: Self) -> bool {
685 true
686 }
687}