argui_animation/
schedule.rs1use crate::Duration;
2
3#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
4pub struct CueId(usize);
5
6impl CueId {
7 #[must_use]
8 pub const fn from_index(index: usize) -> Self {
9 Self(index)
10 }
11
12 #[must_use]
13 pub const fn index(self) -> usize {
14 self.0
15 }
16}
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub struct Cue {
20 pub start: Duration,
21 pub duration: Duration,
22}
23
24impl Cue {
25 #[must_use]
26 pub fn progress(self, elapsed: Duration) -> f32 {
27 if self.duration == Duration::ZERO {
28 return (elapsed >= self.start) as u8 as f32;
29 }
30 let local = elapsed.as_nanos().saturating_sub(self.start.as_nanos());
31 (local as f32 / self.duration.as_nanos() as f32).clamp(0.0, 1.0)
32 }
33}
34
35#[derive(Clone, Debug, Default, Eq, PartialEq)]
36pub struct Schedule {
37 cues: Box<[Cue]>,
38 duration: Duration,
39}
40
41impl Schedule {
42 #[must_use]
43 pub fn sequence(durations: impl IntoIterator<Item = Duration>) -> Self {
44 let mut builder = ScheduleBuilder::new();
45 for duration in durations {
46 builder.then(duration);
47 }
48 builder.build()
49 }
50
51 #[must_use]
52 pub fn parallel(durations: impl IntoIterator<Item = Duration>) -> Self {
53 let cues: Vec<_> = durations
54 .into_iter()
55 .map(|duration| Cue {
56 start: Duration::ZERO,
57 duration,
58 })
59 .collect();
60 Self::from_cues(cues)
61 }
62
63 #[must_use]
64 pub fn stagger(count: usize, duration: Duration, interval: Duration) -> Self {
65 let cues = (0..count)
66 .map(|index| Cue {
67 start: Duration::from_nanos(interval.as_nanos().saturating_mul(index as u64)),
68 duration,
69 })
70 .collect();
71 Self::from_cues(cues)
72 }
73
74 fn from_cues(cues: Vec<Cue>) -> Self {
75 let duration = cues
76 .iter()
77 .map(|cue| cue.start + cue.duration)
78 .max()
79 .unwrap_or(Duration::ZERO);
80 Self {
81 cues: cues.into_boxed_slice(),
82 duration,
83 }
84 }
85
86 #[must_use]
87 pub fn cue(&self, id: CueId) -> Option<Cue> {
88 self.cues.get(id.0).copied()
89 }
90
91 #[must_use]
92 pub const fn duration(&self) -> Duration {
93 self.duration
94 }
95
96 #[must_use]
97 pub fn len(&self) -> usize {
98 self.cues.len()
99 }
100
101 #[must_use]
102 pub fn is_empty(&self) -> bool {
103 self.cues.is_empty()
104 }
105}
106
107#[derive(Clone, Debug, Default)]
108pub struct ScheduleBuilder {
109 cues: Vec<Cue>,
110 cursor: Duration,
111 group_start: Duration,
112}
113
114impl ScheduleBuilder {
115 #[must_use]
116 pub const fn new() -> Self {
117 Self {
118 cues: Vec::new(),
119 cursor: Duration::ZERO,
120 group_start: Duration::ZERO,
121 }
122 }
123
124 pub fn then(&mut self, duration: Duration) -> CueId {
125 self.group_start = self.cursor;
126 let id = self.push(self.cursor, duration);
127 self.cursor += duration;
128 id
129 }
130
131 pub fn with(&mut self, duration: Duration) -> CueId {
132 let id = self.push(self.group_start, duration);
133 self.cursor = self.cursor.max(self.group_start + duration);
134 id
135 }
136
137 pub fn after(&mut self, dependency: CueId, delay: Duration, duration: Duration) -> CueId {
138 let start = self
139 .cues
140 .get(dependency.0)
141 .map_or(delay, |cue| cue.start + cue.duration + delay);
142 let id = self.push(start, duration);
143 self.cursor = self.cursor.max(start + duration);
144 id
145 }
146
147 fn push(&mut self, start: Duration, duration: Duration) -> CueId {
148 let id = CueId(self.cues.len());
149 self.cues.push(Cue { start, duration });
150 id
151 }
152
153 #[must_use]
154 pub fn build(self) -> Schedule {
155 Schedule::from_cues(self.cues)
156 }
157}