1use std::{any::type_name, fmt::Debug, ops::Range};
4
5use crate::{
6 core_item::{AnyExtractCoreItem, DynItem},
7 utils::rate_functions::linear,
8};
9
10pub trait Eval {
17 type Output;
19
20 fn eval_alpha(&self, _alpha: f64) -> Self::Output {
24 unreachable!("iterative segment has no closed form; drive it via `sample`/`step`")
25 }
26
27 fn sample(&self, time: &SegmentTime) -> Self::Output {
31 self.eval_alpha(time.alpha)
32 }
33
34 fn reset(&mut self) {}
37
38 fn step(&mut self, _time: &SegmentTime) {}
42
43 fn apply_to(self, item: &mut Self::Output) -> Self
45 where
46 Self: Sized,
47 {
48 self.apply_alpha_to(item, 1.0)
49 }
50
51 fn apply_alpha_to(self, item: &mut Self::Output, alpha: f64) -> Self
53 where
54 Self: Sized,
55 {
56 *item = self.eval_alpha(alpha);
57 self
58 }
59}
60
61impl<T, F> Eval for F
62where
63 F: Fn(f64) -> T,
64{
65 type Output = T;
66
67 fn eval_alpha(&self, alpha: f64) -> Self::Output {
68 (self)(alpha)
69 }
70}
71
72#[derive(Debug, Clone, Copy, Default)]
77pub struct SegmentTime {
78 pub global_secs: f64,
80 pub global_delta_secs: f64,
82 pub start_secs: f64,
84 pub duration_secs: f64,
86 pub local_secs: f64,
88 pub local_delta_secs: f64,
90 pub alpha: f64,
92 pub render_frame: u64,
94 pub is_render_frame_boundary: bool,
96}
97
98trait EvalDyn {
100 fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>);
101
102 fn sample_dyn(&self, time: &SegmentTime, output: &mut Vec<DynItem>) {
106 self.eval_alpha_dyn_into(time.alpha, output);
107 }
108
109 fn reset_dyn(&mut self) {}
111
112 fn step_dyn(&mut self, _time: &SegmentTime) {}
115
116 fn info_kind(&self) -> AnimationInfoKind {
117 AnimationInfoKind::Eval
118 }
119
120 fn content_duration_secs(&self) -> f64 {
121 1.0
122 }
123
124 fn child_infos(&self) -> Vec<AnimationInfo> {
125 Vec::new()
126 }
127}
128
129struct StaticDynItems(Vec<DynItem>);
130
131impl EvalDyn for StaticDynItems {
132 fn eval_alpha_dyn_into(&self, _alpha: f64, output: &mut Vec<DynItem>) {
133 output.extend(self.0.iter().cloned());
134 }
135
136 fn info_kind(&self) -> AnimationInfoKind {
137 AnimationInfoKind::Static
138 }
139
140 fn content_duration_secs(&self) -> f64 {
141 0.0
142 }
143}
144
145impl<E> EvalDyn for E
146where
147 E: Eval,
148 E::Output: AnyExtractCoreItem,
149{
150 fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
151 output.push(DynItem(Box::new(self.eval_alpha(alpha))));
154 }
155
156 fn sample_dyn(&self, time: &SegmentTime, output: &mut Vec<DynItem>) {
157 output.push(DynItem(Box::new(self.sample(time))));
158 }
159
160 fn reset_dyn(&mut self) {
161 Eval::reset(self);
162 }
163
164 fn step_dyn(&mut self, time: &SegmentTime) {
165 Eval::step(self, time);
166 }
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub enum AnimationInfoKind {
172 Eval,
174 Sequence,
176 Stack,
178 Static,
180}
181
182#[derive(Clone)]
184pub struct AnimationInfo {
185 pub anim_name: String,
187 pub kind: AnimationInfoKind,
189 pub range: Range<f64>,
191 pub content_duration_secs: f64,
193 pub rate_func: fn(f64) -> f64,
195 pub enabled: bool,
197 pub children: Vec<AnimationInfo>,
199}
200
201pub struct AnimationCell {
203 inner: Box<dyn EvalDyn>,
204 rate_func: fn(f64) -> f64,
205 time_range: Range<f64>,
206 enabled: bool,
207 anim_name: &'static str,
208 entered: bool,
210}
211
212impl AnimationCell {
213 pub fn time_range(&self) -> Range<f64> {
215 self.time_range.clone()
216 }
217
218 pub fn duration_secs(&self) -> f64 {
220 self.time_range.end - self.time_range.start
221 }
222
223 fn shift_by(&mut self, offset_sec: f64) {
224 self.time_range.start += offset_sec;
225 self.time_range.end += offset_sec;
226 }
227
228 pub fn enabled(&self) -> bool {
230 self.enabled
231 }
232
233 pub fn anim_name(&self) -> &str {
235 self.anim_name
236 }
237
238 pub fn active_at(&self, sec: f64) -> bool {
240 sec >= self.time_range.start && sec <= self.time_range.end
241 }
242
243 fn local_time(&self, sec: f64, prev_sec: f64) -> SegmentTime {
245 let duration = self.duration_secs();
246 let alpha = if duration == 0.0 {
247 1.0
248 } else {
249 (sec - self.time_range.start) / duration
250 };
251 let prev_alpha = if duration == 0.0 {
252 1.0
253 } else {
254 (prev_sec - self.time_range.start) / duration
255 };
256 let rate_alpha = (self.rate_func)(alpha);
257 let rate_prev = (self.rate_func)(prev_alpha);
258 SegmentTime {
259 global_secs: sec,
260 global_delta_secs: sec - prev_sec,
261 start_secs: self.time_range.start,
262 duration_secs: duration,
263 local_secs: rate_alpha * duration,
264 local_delta_secs: (rate_alpha - rate_prev) * duration,
265 alpha: rate_alpha,
266 render_frame: 0,
267 is_render_frame_boundary: false,
268 }
269 }
270
271 pub(crate) fn reset_entered(&mut self) {
273 self.entered = false;
274 }
275
276 pub(crate) fn sample_at_sec(&self, sec: f64, output: &mut Vec<DynItem>) {
278 if !self.active_at(sec) || !self.enabled {
279 return;
280 }
281 let time = self.local_time(sec, sec);
282 self.inner.sample_dyn(&time, output);
283 }
284
285 pub(crate) fn step_at_sec(&mut self, sec: f64, prev_sec: f64) {
291 if !self.active_at(sec) || !self.enabled {
292 return;
293 }
294 if !self.entered {
295 self.inner.reset_dyn();
296 self.entered = true;
297 }
298 let time = self.local_time(sec, prev_sec);
299 self.inner.step_dyn(&time);
300 }
301
302 pub fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
304 if self.enabled {
305 self.inner
306 .eval_alpha_dyn_into((self.rate_func)(alpha), output);
307 }
308 }
309
310 pub fn eval_alpha_dyn(&self, alpha: f64) -> Vec<DynItem> {
312 let mut output = Vec::new();
313 self.eval_alpha_dyn_into(alpha, &mut output);
314 output
315 }
316
317 pub fn eval_at_sec(&self, sec: f64) -> Option<Vec<DynItem>> {
319 if sec < self.time_range.start || sec > self.time_range.end {
320 return None;
321 }
322 let duration = self.duration_secs();
323 let alpha = if duration == 0.0 {
324 1.0
325 } else {
326 (sec - self.time_range.start) / duration
327 };
328 Some(self.eval_alpha_dyn(alpha))
329 }
330
331 fn contains_sec(&self, sec: f64, parent_duration: f64) -> bool {
332 self.time_range.contains(&sec) || (sec == parent_duration && sec == self.time_range.end)
333 }
334
335 pub(crate) fn animation_info(&self) -> AnimationInfo {
336 AnimationInfo {
337 anim_name: self.anim_name.to_string(),
338 kind: self.inner.info_kind(),
339 range: self.time_range.clone(),
340 content_duration_secs: self.inner.content_duration_secs(),
341 rate_func: self.rate_func,
342 enabled: self.enabled,
343 children: self.inner.child_infos(),
344 }
345 }
346}
347
348pub trait Animation: Sized {
350 fn build(self) -> AnimationCell;
352}
353
354pub trait Placeable: Animation {
359 fn at(self, offset_sec: f64) -> At<Self> {
361 At {
362 inner: self,
363 offset_sec,
364 }
365 }
366}
367
368pub trait AnimationExt: Placeable {
370 fn with_rate_func(self, rate_func: fn(f64) -> f64) -> Paramed<Self> {
372 Paramed::new(self).with_rate_func(rate_func)
373 }
374
375 fn with_duration(self, duration_secs: f64) -> Paramed<Self> {
377 Paramed::new(self).with_duration(duration_secs)
378 }
379
380 fn with_enabled(self, enabled: bool) -> Paramed<Self> {
382 Paramed::new(self).with_enabled(enabled)
383 }
384}
385
386impl<A: Placeable> AnimationExt for A {}
387
388impl<E> Placeable for E
389where
390 E: Eval + 'static,
391 E::Output: AnyExtractCoreItem,
392{
393}
394impl<E> Animation for E
395where
396 E: Eval + 'static,
397 E::Output: AnyExtractCoreItem,
398{
399 fn build(self) -> AnimationCell {
400 AnimationCell {
401 anim_name: type_name::<E>(),
402 inner: Box::new(self),
403 rate_func: linear,
404 time_range: 0.0..1.0,
405 enabled: true,
406 entered: false,
407 }
408 }
409}
410
411#[derive(Debug, Clone)]
413pub(crate) struct AnimationParam {
414 pub rate_func: fn(f64) -> f64,
416 pub duration_secs: Option<f64>,
418 pub enabled: bool,
420}
421
422impl Default for AnimationParam {
423 fn default() -> Self {
424 Self {
425 rate_func: linear,
426 duration_secs: None,
427 enabled: true,
428 }
429 }
430}
431
432pub struct Paramed<A> {
434 inner: A,
435 param: AnimationParam,
436}
437
438impl<A> Paramed<A> {
439 pub(crate) fn new(inner: A) -> Self {
441 Self {
442 inner,
443 param: AnimationParam::default(),
444 }
445 }
446
447 pub fn with_rate_func(mut self, rate_func: fn(f64) -> f64) -> Self {
449 self.param.rate_func = rate_func;
450 self
451 }
452
453 pub fn with_duration(mut self, duration_secs: f64) -> Self {
455 assert_valid_duration(duration_secs);
456 self.param.duration_secs = Some(duration_secs);
457 self
458 }
459
460 pub fn with_enabled(mut self, enabled: bool) -> Self {
462 self.param.enabled = enabled;
463 self
464 }
465}
466
467impl<A: Placeable + 'static> Placeable for Paramed<A> {}
468impl<A: Placeable + 'static> Animation for Paramed<A> {
469 fn build(self) -> AnimationCell {
470 let mut cell = self.inner.build();
471 if let Some(duration_secs) = self.param.duration_secs {
472 cell.time_range = 0.0..duration_secs;
473 }
474 cell.rate_func = self.param.rate_func;
475 cell.enabled = self.param.enabled;
476 cell.anim_name = type_name::<A>();
477 cell
478 }
479}
480
481pub struct At<A> {
487 inner: A,
488 offset_sec: f64,
489}
490
491impl<A: Animation> Animation for At<A> {
492 fn build(self) -> AnimationCell {
493 let mut animation = self.inner.build();
494 animation.shift_by(self.offset_sec);
495 animation
496 }
497}
498
499fn assert_valid_duration(duration_secs: f64) {
500 assert!(
501 duration_secs.is_finite() && duration_secs >= 0.0,
502 "animation duration must be finite and non-negative"
503 );
504}
505
506fn map_content_time(time: &SegmentTime, content_duration: f64) -> (f64, f64) {
512 let cell_duration = time.duration_secs;
513 if cell_duration <= 0.0 {
514 return (0.0, 0.0);
515 }
516 let content_sec = content_duration * (time.local_secs / cell_duration);
517 let prev_content_sec =
518 content_duration * ((time.local_secs - time.local_delta_secs) / cell_duration);
519 (content_sec, prev_content_sec)
520}
521
522#[derive(Default)]
527pub struct AnimSequence {
528 animations: Vec<AnimationCell>,
529 cursor_sec: f64,
530}
531
532impl AnimSequence {
533 pub fn new() -> Self {
535 Self::default()
536 }
537
538 fn eval_at_sec_into(&self, target_sec: f64, output: &mut Vec<DynItem>) {
539 if let Some(animation) = self
540 .animations
541 .iter()
542 .rev()
543 .find(|animation| animation.contains_sec(target_sec, self.cursor_sec))
544 {
545 animation.sample_at_sec(target_sec, output);
546 }
547 }
548
549 pub fn push<A: Placeable + 'static>(&mut self, animation: A) -> &mut Self {
551 let mut animation = animation.build();
552 let duration_secs = animation.duration_secs();
553 animation.shift_by(self.cursor_sec);
554 self.animations.push(animation);
555 self.cursor_sec += duration_secs;
556 self
557 }
558
559 pub fn extend(&mut self, mut sequence: AnimSequence) -> &mut Self {
561 for animation in &mut sequence.animations {
562 animation.shift_by(self.cursor_sec);
563 }
564 self.animations.extend(sequence.animations);
565 self.cursor_sec += sequence.cursor_sec;
566 self
567 }
568
569 pub fn forward(&mut self, secs: f64) -> &mut Self {
571 assert!(
572 secs.is_finite() && secs >= 0.0,
573 "forward duration must be finite and non-negative"
574 );
575 self.cursor_sec += secs;
576 self
577 }
578
579 pub fn forward_to(&mut self, target_sec: f64) -> &mut Self {
581 assert!(
582 target_sec.is_finite() && target_sec >= 0.0,
583 "forward target must be finite and non-negative"
584 );
585 if target_sec > self.cursor_sec {
586 self.forward(target_sec - self.cursor_sec);
587 }
588 self
589 }
590
591 pub fn hold(&mut self, secs: f64) -> &mut Self {
593 assert!(
594 secs.is_finite() && secs >= 0.0,
595 "hold duration must be finite and non-negative"
596 );
597 if secs == 0.0 {
598 return self;
599 }
600
601 let mut state = Vec::new();
602 self.eval_at_sec_into(self.cursor_sec, &mut state);
603
604 if !state.is_empty() {
605 self.animations.push(AnimationCell {
606 inner: Box::new(StaticDynItems(state)),
607 rate_func: linear,
608 time_range: self.cursor_sec..self.cursor_sec + secs,
609 enabled: true,
610 anim_name: type_name::<StaticDynItems>(),
611 entered: false,
612 });
613 }
614 self.cursor_sec += secs;
615 self
616 }
617
618 pub fn hold_to(&mut self, target_sec: f64) -> &mut Self {
620 assert!(
621 target_sec.is_finite() && target_sec >= 0.0,
622 "hold target must be finite and non-negative"
623 );
624 if target_sec > self.cursor_sec {
625 self.hold(target_sec - self.cursor_sec);
626 }
627 self
628 }
629
630 pub fn cursor_sec(&self) -> f64 {
632 self.cursor_sec
633 }
634
635 pub fn duration_secs(&self) -> f64 {
637 self.cursor_sec
638 }
639
640 pub fn built_animations(&self) -> &[AnimationCell] {
642 &self.animations
643 }
644
645 pub fn into_built_animations(self) -> Vec<AnimationCell> {
647 self.animations
648 }
649}
650
651impl Placeable for AnimSequence {}
652impl Animation for AnimSequence {
653 fn build(self) -> AnimationCell {
654 let duration_secs = self.cursor_sec;
655 AnimationCell {
656 inner: Box::new(self),
657 rate_func: linear,
658 time_range: 0.0..duration_secs,
659 enabled: true,
660 anim_name: type_name::<Self>(),
661 entered: false,
662 }
663 }
664}
665
666impl EvalDyn for AnimSequence {
667 fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
668 self.eval_at_sec_into(self.cursor_sec * alpha, output);
669 }
670
671 fn step_dyn(&mut self, time: &SegmentTime) {
672 let (content_sec, prev_content_sec) = map_content_time(time, self.cursor_sec);
675 if let Some(child) = self
676 .animations
677 .iter_mut()
678 .rev()
679 .find(|child| child.contains_sec(content_sec, self.cursor_sec))
680 {
681 child.step_at_sec(content_sec, prev_content_sec);
682 }
683 }
684
685 fn info_kind(&self) -> AnimationInfoKind {
686 AnimationInfoKind::Sequence
687 }
688
689 fn content_duration_secs(&self) -> f64 {
690 self.cursor_sec
691 }
692
693 fn child_infos(&self) -> Vec<AnimationInfo> {
694 self.animations
695 .iter()
696 .map(AnimationCell::animation_info)
697 .collect()
698 }
699}
700
701#[macro_export]
703macro_rules! seq {
704 ($($animation:expr),* $(,)?) => {
705 {
706 #[allow(unused_mut)]
707 let mut sequence = $crate::animation::AnimSequence::new();
708 $(sequence.push($animation);)*
709 sequence
710 }
711 };
712}
713
714#[derive(Default)]
719pub struct AnimStack {
720 animations: Vec<AnimationCell>,
721 duration_secs: f64,
722}
723
724impl AnimStack {
725 pub fn new() -> Self {
727 Self::default()
728 }
729
730 fn eval_at_sec_into(&self, target_sec: f64, output: &mut Vec<DynItem>) {
731 for animation in &self.animations {
732 if animation.contains_sec(target_sec, self.duration_secs) {
733 animation.sample_at_sec(target_sec, output);
734 }
735 }
736 }
737
738 pub fn push<A: Animation + 'static>(&mut self, animation: A) -> &mut Self {
740 let animation = animation.build();
741 self.duration_secs = self.duration_secs.max(animation.time_range.end);
742 self.animations.push(animation);
743 self
744 }
745
746 pub fn extend(&mut self, stack: AnimStack) -> &mut Self {
748 self.duration_secs = self.duration_secs.max(stack.duration_secs);
749 self.animations.extend(stack.animations);
750 self
751 }
752
753 pub fn duration_secs(&self) -> f64 {
755 self.duration_secs
756 }
757
758 pub fn built_animations(&self) -> &[AnimationCell] {
760 &self.animations
761 }
762
763 pub fn into_built_animations(self) -> Vec<AnimationCell> {
765 self.animations
766 }
767}
768
769impl Placeable for AnimStack {}
770impl Animation for AnimStack {
771 fn build(self) -> AnimationCell {
772 let duration_secs = self.duration_secs;
773 AnimationCell {
774 inner: Box::new(self),
775 rate_func: linear,
776 time_range: 0.0..duration_secs,
777 enabled: true,
778 anim_name: type_name::<Self>(),
779 entered: false,
780 }
781 }
782}
783
784impl EvalDyn for AnimStack {
785 fn eval_alpha_dyn_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
786 self.eval_at_sec_into(self.duration_secs * alpha, output);
787 }
788
789 fn step_dyn(&mut self, time: &SegmentTime) {
790 let (content_sec, prev_content_sec) = map_content_time(time, self.duration_secs);
791 for child in &mut self.animations {
792 if child.contains_sec(content_sec, self.duration_secs) {
793 child.step_at_sec(content_sec, prev_content_sec);
794 }
795 }
796 }
797
798 fn info_kind(&self) -> AnimationInfoKind {
799 AnimationInfoKind::Stack
800 }
801
802 fn content_duration_secs(&self) -> f64 {
803 self.duration_secs
804 }
805
806 fn child_infos(&self) -> Vec<AnimationInfo> {
807 self.animations
808 .iter()
809 .map(AnimationCell::animation_info)
810 .collect()
811 }
812}
813
814#[macro_export]
816macro_rules! stack {
817 ($($animation:expr),* $(,)?) => {
818 {
819 #[allow(unused_mut)]
820 let mut stack = $crate::animation::AnimStack::new();
821 $(stack.push($animation);)*
822 stack
823 }
824 };
825}
826
827pub trait StaticAnimRequirement: Clone + AnyExtractCoreItem {}
829
830impl<T: Clone + AnyExtractCoreItem> StaticAnimRequirement for T {}
831
832pub trait StaticAnim: StaticAnimRequirement + Sized {
834 fn show(&self) -> Paramed<Static<Self>>;
836 fn hide(&self) -> Paramed<Static<Self>>;
838}
839
840impl<T: StaticAnimRequirement + 'static> StaticAnim for T {
841 fn show(&self) -> Paramed<Static<Self>> {
842 Static(self.clone()).with_duration(0.0)
843 }
844
845 fn hide(&self) -> Paramed<Static<Self>> {
846 Static(self.clone()).with_enabled(false).with_duration(0.0)
847 }
848}
849
850pub struct Static<T: Clone>(pub T);
852
853impl<T: Clone> Eval for Static<T> {
854 type Output = T;
855
856 fn eval_alpha(&self, _alpha: f64) -> Self::Output {
857 self.0.clone()
858 }
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::{Extract, core_item::CoreItem, core_item::vitem::VItem};
865
866 fn leaf(x: f32, duration: f64) -> impl Placeable {
867 (move |_alpha| {
868 let mut item = VItem::default();
869 item.points[0].x = x;
870 item
871 })
872 .with_duration(duration)
873 }
874
875 fn progress_leaf(offset: f32) -> impl Placeable {
876 move |alpha| {
877 let mut item = VItem::default();
878 item.points[0].x = offset + alpha as f32;
879 item
880 }
881 }
882
883 fn evaluated_xs(items: Vec<DynItem>) -> Vec<f32> {
884 items
885 .into_iter()
886 .flat_map(|item| item.extract())
887 .filter_map(|item| match item {
888 CoreItem::VItem(item) => Some(item.points[0].x),
889 CoreItem::CameraFrame(_) | CoreItem::MeshItem(_) => None,
890 })
891 .collect()
892 }
893
894 #[test]
895 fn at_offsets_the_built_animation() {
896 let animation = leaf(1.0, 2.0).at(3.0).build();
897 assert_eq!(animation.time_range(), 3.0..5.0);
898 }
899
900 #[test]
901 fn eval_uses_linear_one_second_defaults() {
902 let animation = Static(VItem::default()).build();
903 assert_eq!(animation.time_range(), 0.0..1.0);
904 }
905
906 #[test]
907 fn parametrized_sequence_remaps_the_group_timeline() {
908 use crate::utils::rate_functions::ease_in_quad;
909
910 let animation = seq![progress_leaf(0.0), progress_leaf(10.0)]
911 .with_duration(4.0)
912 .with_rate_func(ease_in_quad);
913 let animation = animation.build();
914
915 assert_eq!(animation.time_range(), 0.0..4.0);
916 let items = animation.eval_at_sec(2.0).unwrap();
917 assert_eq!(evaluated_xs(items), vec![0.5]);
918 let info = animation.animation_info();
919 assert_eq!(info.range, 0.0..4.0);
920 assert_eq!(info.content_duration_secs, 2.0);
921 assert_eq!(info.children.len(), 2);
922 assert_eq!(info.children[0].range, 0.0..1.0);
923 assert_eq!(info.children[1].range, 1.0..2.0);
924 }
925
926 #[test]
927 fn seq_uses_child_durations() {
928 let sequence = seq![leaf(1.0, 2.0), leaf(2.0, 3.0)];
929 assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
930 assert_eq!(sequence.built_animations()[1].time_range(), 2.0..5.0);
931 assert_eq!(sequence.at(5.0).build().time_range(), 5.0..10.0);
932 }
933
934 #[test]
935 fn stack_accepts_plain_and_positioned_children() {
936 let animation = stack![leaf(1.0, 2.0), leaf(2.0, 3.0).at(1.0)];
937 assert_eq!(animation.duration_secs(), 4.0);
938 assert_eq!(animation.built_animations()[0].time_range(), 0.0..2.0);
939 assert_eq!(animation.built_animations()[1].time_range(), 1.0..4.0);
940 assert_eq!(animation.at(10.0).build().time_range(), 10.0..14.0);
941 }
942
943 #[test]
944 fn composition_macros_build_dynamic_containers_without_an_arity_limit() {
945 let empty_sequence: AnimSequence = seq![];
946 let empty_stack: AnimStack = stack![];
947 assert_eq!(empty_sequence.duration_secs(), 0.0);
948 assert_eq!(empty_stack.duration_secs(), 0.0);
949
950 let sequence: AnimSequence = seq![
951 leaf(1.0, 1.0),
952 leaf(2.0, 1.0),
953 leaf(3.0, 1.0),
954 leaf(4.0, 1.0),
955 leaf(5.0, 1.0),
956 leaf(6.0, 1.0),
957 leaf(7.0, 1.0),
958 leaf(8.0, 1.0),
959 leaf(9.0, 1.0),
960 ];
961 assert_eq!(sequence.duration_secs(), 9.0);
962 assert_eq!(sequence.built_animations().len(), 9);
963
964 let stack: AnimStack = stack![
965 leaf(1.0, 1.0),
966 leaf(2.0, 2.0),
967 leaf(3.0, 3.0),
968 leaf(4.0, 4.0),
969 leaf(5.0, 5.0),
970 leaf(6.0, 6.0),
971 leaf(7.0, 7.0),
972 leaf(8.0, 8.0),
973 leaf(9.0, 9.0),
974 ];
975 assert_eq!(stack.duration_secs(), 9.0);
976 assert_eq!(stack.built_animations().len(), 9);
977 }
978
979 #[test]
980 fn sequence_can_be_repositioned_after_erasure() {
981 let mut sequence = AnimSequence::new();
982 sequence
983 .push(leaf(1.0, 2.0))
984 .forward(1.0)
985 .push(leaf(2.0, 1.0));
986 let animation = sequence.at(10.0).build();
987 let info = animation.animation_info();
988 assert_eq!(info.range, 10.0..14.0);
989 assert_eq!(info.children[0].range, 0.0..2.0);
990 assert_eq!(info.children[1].range, 3.0..4.0);
991 }
992
993 #[test]
994 fn hold_samples_only_animations_active_before_the_cursor() {
995 let mut sequence = AnimSequence::new();
996 sequence
997 .push(stack![leaf(1.0, 1.0), leaf(2.0, 2.0)])
998 .hold(1.0);
999
1000 assert_eq!(sequence.built_animations().len(), 2);
1001 let held = sequence.built_animations()[1].eval_at_sec(2.5).unwrap();
1002 assert_eq!(evaluated_xs(held), vec![2.0]);
1003 }
1004
1005 #[test]
1006 fn repeated_hold_creates_adjacent_static_animations() {
1007 let mut sequence = AnimSequence::new();
1008 sequence.push(leaf(3.0, 1.0)).hold(1.0).hold(2.0);
1009
1010 assert_eq!(sequence.cursor_sec(), 4.0);
1011 assert_eq!(sequence.built_animations().len(), 3);
1012 assert_eq!(sequence.built_animations()[1].time_range(), 1.0..2.0);
1013 assert_eq!(sequence.built_animations()[2].time_range(), 2.0..4.0);
1014 let held = sequence.built_animations()[2].eval_at_sec(3.5).unwrap();
1015 assert_eq!(evaluated_xs(held), vec![3.0]);
1016 }
1017
1018 #[test]
1019 fn repeated_hold_replays_dyn_items_without_nesting_the_output_batch() {
1020 let mut sequence = AnimSequence::new();
1021 sequence
1022 .push(stack![leaf(1.0, 1.0), leaf(2.0, 1.0)])
1023 .hold(1.0)
1024 .hold(1.0);
1025
1026 let first_hold = sequence.built_animations()[1].eval_at_sec(1.5).unwrap();
1027 let second_hold = sequence.built_animations()[2].eval_at_sec(2.5).unwrap();
1028
1029 assert_eq!(first_hold.len(), 2);
1030 assert_eq!(second_hold.len(), 2);
1031 assert_eq!(evaluated_xs(second_hold), vec![1.0, 2.0]);
1032 }
1033
1034 #[test]
1035 fn forward_does_not_hold_the_previous_state() {
1036 let mut sequence = AnimSequence::new();
1037 sequence.push(leaf(4.0, 1.0)).forward(1.0).hold(1.0);
1038
1039 assert_eq!(sequence.cursor_sec(), 3.0);
1040 assert_eq!(sequence.built_animations().len(), 1);
1041 }
1042
1043 #[test]
1044 fn hold_uses_the_sequences_final_evaluation() {
1045 let mut shown = VItem::default();
1046 shown.points[0].x = 5.0;
1047
1048 let mut hidden = AnimSequence::new();
1049 hidden.push(leaf(1.0, 1.0)).push(shown.hide()).hold(1.0);
1050 assert_eq!(hidden.built_animations().len(), 2);
1051 assert!(
1052 hidden.built_animations()[1]
1053 .eval_at_sec(1.0)
1054 .unwrap()
1055 .is_empty()
1056 );
1057
1058 let mut restored = AnimSequence::new();
1059 restored.push(leaf(1.0, 1.0)).push(shown.show()).hold(1.0);
1060 let held = restored.built_animations()[2].eval_at_sec(1.5).unwrap();
1061 assert_eq!(evaluated_xs(held), vec![5.0]);
1062 }
1063
1064 #[test]
1065 fn nested_sequences_keep_their_own_final_evaluation() {
1066 let mut shown = VItem::default();
1067 shown.points[0].x = 7.0;
1068
1069 let inner = seq![leaf(1.0, 1.0), shown.show()];
1070 let mut outer = AnimSequence::new();
1071 outer.push(inner).hold(1.0);
1072
1073 assert_eq!(outer.built_animations().len(), 2);
1074 let held = outer.built_animations()[1].eval_at_sec(1.5).unwrap();
1075 assert_eq!(evaluated_xs(held), vec![7.0]);
1076
1077 let hidden_inner = seq![leaf(1.0, 1.0), shown.hide()];
1078 let mut hidden_outer = AnimSequence::new();
1079 hidden_outer.push(hidden_inner).hold(1.0);
1080 assert_eq!(hidden_outer.built_animations().len(), 1);
1081 }
1082
1083 #[test]
1084 fn dynamic_stack_keeps_children_at_the_same_origin() {
1085 let mut stack = AnimStack::new();
1086 stack.push(leaf(1.0, 1.0)).push(leaf(2.0, 3.0));
1087
1088 assert_eq!(stack.duration_secs(), 3.0);
1089 assert_eq!(stack.built_animations()[0].time_range(), 0.0..1.0);
1090 assert_eq!(stack.built_animations()[1].time_range(), 0.0..3.0);
1091 }
1092
1093 #[test]
1094 fn sequence_extend_appends_direct_children_and_preserves_local_gaps() {
1095 let mut source = AnimSequence::new();
1096 source
1097 .push(leaf(2.0, 1.0))
1098 .forward(2.0)
1099 .push(leaf(3.0, 1.0));
1100
1101 let mut sequence = AnimSequence::new();
1102 sequence.push(leaf(1.0, 2.0)).extend(source);
1103
1104 assert_eq!(sequence.cursor_sec(), 6.0);
1105 assert_eq!(sequence.built_animations().len(), 3);
1106 assert_eq!(sequence.built_animations()[0].time_range(), 0.0..2.0);
1107 assert_eq!(sequence.built_animations()[1].time_range(), 2.0..3.0);
1108 assert_eq!(sequence.built_animations()[2].time_range(), 5.0..6.0);
1109 }
1110
1111 #[test]
1112 fn stack_extend_appends_direct_children() {
1113 let source = stack![leaf(2.0, 1.0), leaf(3.0, 2.0).at(1.0)];
1114 let mut stack = stack![leaf(1.0, 4.0)];
1115 stack.extend(source);
1116
1117 assert_eq!(stack.duration_secs(), 4.0);
1118 assert_eq!(stack.built_animations().len(), 3);
1119 assert_eq!(stack.built_animations()[1].time_range(), 0.0..1.0);
1120 assert_eq!(stack.built_animations()[2].time_range(), 1.0..3.0);
1121 }
1122}