1use gpui::SharedString;
15
16use super::motion::fold_time;
17use super::{Frame, Loop, Motion};
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum At {
22 End,
24 Abs(f32),
26 Rel(f32),
29 With(f32),
32 Label(SharedString, f32),
34}
35
36impl At {
37 pub fn with_previous() -> Self {
39 At::With(0.0)
40 }
41}
42
43#[derive(Debug, Clone, PartialEq)]
44struct Entry {
45 start: f32,
46 motion: Motion,
47}
48
49#[derive(Debug, Clone, Default, PartialEq)]
51pub struct Sequence {
52 entries: Vec<Entry>,
53 labels: Vec<(SharedString, f32)>,
54 loops: Loop,
55 alternate: bool,
56 reversed: bool,
57 previous_start: f32,
58}
59
60impl Sequence {
61 pub fn new() -> Self {
62 Sequence::default()
63 }
64
65 #[allow(clippy::should_implement_trait)]
69 pub fn add(self, motion: Motion) -> Self {
70 self.add_at(motion, At::End)
71 }
72
73 pub fn add_at(mut self, motion: Motion, at: At) -> Self {
74 let start = self.resolve(&at).max(0.0);
75 self.previous_start = start;
76 self.entries.push(Entry { start, motion });
77 self
78 }
79
80 pub fn label(mut self, name: impl Into<SharedString>, at: At) -> Self {
82 let position = self.resolve(&at).max(0.0);
83 self.labels.push((name.into(), position));
84 self
85 }
86
87 pub fn loops(mut self, loops: Loop) -> Self {
88 self.loops = loops;
89 self
90 }
91
92 pub fn repeat(mut self, times: u32) -> Self {
93 self.loops = Loop::Times(times);
94 self
95 }
96
97 pub fn repeat_forever(mut self) -> Self {
98 self.loops = Loop::Forever;
99 self
100 }
101
102 pub fn alternate(mut self, alternate: bool) -> Self {
103 self.alternate = alternate;
104 self
105 }
106
107 pub fn reversed(mut self, reversed: bool) -> Self {
108 self.reversed = reversed;
109 self
110 }
111
112 pub fn is_empty(&self) -> bool {
113 self.entries.is_empty()
114 }
115
116 pub fn is_alternating(&self) -> bool {
118 self.alternate
119 }
120
121 pub fn len(&self) -> usize {
122 self.entries.len()
123 }
124
125 pub fn resolve(&self, at: &At) -> f32 {
127 match at {
128 At::End => self.iteration_ms(),
129 At::Abs(ms) => *ms,
130 At::Rel(ms) => self.iteration_ms() + ms,
131 At::With(ms) => self.previous_start + ms,
132 At::Label(name, ms) => {
133 let base = self
134 .labels
135 .iter()
136 .find(|(label, _)| label == name)
137 .map(|(_, position)| *position)
138 .unwrap_or(0.0);
139 base + ms
140 }
141 }
142 }
143
144 pub fn iteration_ms(&self) -> f32 {
148 self
149 .entries
150 .iter()
151 .map(|entry| {
152 let span = match entry.motion.loops {
153 Loop::Forever => entry.motion.iteration_ms(),
154 _ => entry.motion.total_ms(),
155 };
156 entry.start + span
157 })
158 .fold(0.0_f32, f32::max)
159 }
160
161 pub fn total_ms(&self) -> f32 {
162 match self.loops.count() {
163 Some(n) => self.iteration_ms() * n as f32,
164 None => f32::INFINITY,
165 }
166 }
167
168 pub fn sample(&self, t: f32) -> Frame {
169 let mut frame = Frame::new();
170 self.sample_into(t, &mut frame);
171 frame
172 }
173
174 pub fn sample_into(&self, t: f32, frame: &mut Frame) {
175 let (local, progress, finished) = fold_time(
178 t,
179 self.iteration_ms(),
180 self.loops,
181 self.alternate,
182 self.reversed,
183 );
184
185 for entry in &self.entries {
186 if local + f32::EPSILON >= entry.start {
189 entry.motion.sample_into(local - entry.start, frame);
190 }
191 }
192
193 frame.progress = progress;
194 frame.finished = finished;
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::anim::{Easing, Prop};
202
203 fn leg(prop: Prop, from: f32, to: f32, ms: f32) -> Motion {
204 Motion::new()
205 .duration(ms)
206 .ease(Easing::Linear)
207 .tween(prop, from, to)
208 }
209
210 #[test]
211 fn entries_queue_up_end_to_end() {
212 let sequence = Sequence::new()
213 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
214 .add(leg(Prop::X, 0.0, 50.0, 100.0));
215 assert_eq!(sequence.iteration_ms(), 200.0);
216
217 let early = sequence.sample(50.0);
218 assert!((early.number(Prop::Opacity).unwrap() - 0.5).abs() < 1e-5);
219 assert_eq!(early.number(Prop::X), None, "not its turn yet");
220
221 let late = sequence.sample(150.0);
222 assert_eq!(late.number(Prop::Opacity), Some(1.0));
223 assert!((late.number(Prop::X).unwrap() - 25.0).abs() < 1e-4);
224 }
225
226 #[test]
227 fn relative_placement_overlaps_the_tail() {
228 let sequence = Sequence::new()
229 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
230 .add_at(leg(Prop::X, 0.0, 50.0, 100.0), At::Rel(-50.0));
231 assert_eq!(sequence.iteration_ms(), 150.0);
232 let mid = sequence.sample(75.0);
233 assert!(mid.number(Prop::Opacity).unwrap() > 0.5);
234 assert!(mid.number(Prop::X).unwrap() > 0.0);
235 }
236
237 #[test]
238 fn with_previous_starts_them_together() {
239 let sequence = Sequence::new()
240 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
241 .add_at(leg(Prop::X, 0.0, 50.0, 100.0), At::with_previous());
242 assert_eq!(sequence.iteration_ms(), 100.0);
243 let mid = sequence.sample(50.0);
244 assert!((mid.number(Prop::Opacity).unwrap() - 0.5).abs() < 1e-5);
245 assert!((mid.number(Prop::X).unwrap() - 25.0).abs() < 1e-4);
246 }
247
248 #[test]
249 fn labels_anchor_later_entries() {
250 let sequence = Sequence::new()
251 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
252 .label("settled", At::End)
253 .add(leg(Prop::X, 0.0, 50.0, 100.0))
254 .add_at(
255 leg(Prop::Y, 0.0, 10.0, 50.0),
256 At::Label("settled".into(), 25.0),
257 );
258 assert_eq!(sequence.resolve(&At::Label("settled".into(), 0.0)), 100.0);
259 assert_eq!(sequence.sample(120.0).number(Prop::Y), None);
261 assert!(sequence.sample(130.0).number(Prop::Y).is_some());
262 }
263
264 #[test]
265 fn a_missing_label_falls_back_to_the_start() {
266 let sequence = Sequence::new().add(leg(Prop::X, 0.0, 1.0, 100.0));
267 assert_eq!(sequence.resolve(&At::Label("nope".into(), 10.0)), 10.0);
268 }
269
270 #[test]
271 fn the_last_writer_wins_when_motions_overlap() {
272 let sequence = Sequence::new()
273 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
274 .add_at(leg(Prop::Opacity, 1.0, 0.0, 100.0), At::with_previous());
275 assert!((sequence.sample(50.0).number(Prop::Opacity).unwrap() - 0.5).abs() < 1e-5);
276 assert_eq!(sequence.sample(100.0).number(Prop::Opacity), Some(0.0));
277 }
278
279 #[test]
280 fn a_forever_child_does_not_make_the_sequence_infinite() {
281 let sequence = Sequence::new().add(leg(Prop::Opacity, 0.0, 1.0, 100.0).repeat_forever());
282 assert_eq!(sequence.iteration_ms(), 100.0);
283 assert!(sequence.total_ms().is_finite());
284 }
285
286 #[test]
287 fn the_sequence_can_loop_as_a_whole() {
288 let sequence = Sequence::new()
289 .add(leg(Prop::Opacity, 0.0, 1.0, 100.0))
290 .repeat(2);
291 assert_eq!(sequence.total_ms(), 200.0);
292 assert!((sequence.sample(150.0).number(Prop::Opacity).unwrap() - 0.5).abs() < 1e-5);
293 assert!(sequence.sample(200.0).finished);
294 }
295}