1use std::time::{Duration, Instant};
17
18use gpui::{Context, EventEmitter, Task, Window};
19
20use super::{Clip, Frame};
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AnimatorEvent {
27 Begin,
29 Complete,
31}
32
33pub struct Animator {
35 clip: Clip,
36 time: f32,
38 anchor: Option<Instant>,
40 speed: f32,
41 reversed: bool,
42 epoch: usize,
44 completion: Option<Task<()>>,
45}
46
47impl EventEmitter<AnimatorEvent> for Animator {}
48
49impl Animator {
50 pub fn new(clip: impl Into<Clip>, _cx: &mut Context<Self>) -> Self {
51 Animator {
52 clip: clip.into(),
53 time: 0.0,
54 anchor: None,
55 speed: 1.0,
56 reversed: false,
57 epoch: 0,
58 completion: None,
59 }
60 }
61
62 pub fn autoplay(mut self, cx: &mut Context<Self>) -> Self {
64 self.play(cx);
65 self
66 }
67
68 pub fn reversed(mut self, reversed: bool) -> Self {
70 self.reversed = reversed;
71 self.time = if reversed { self.total_ms() } else { 0.0 };
72 self
73 }
74
75 pub fn clip(&self) -> &Clip {
76 &self.clip
77 }
78
79 fn total_ms(&self) -> f32 {
80 self.clip.total_ms()
81 }
82
83 pub fn time(&self) -> f32 {
85 self.time_at(Instant::now())
86 }
87
88 pub fn time_at(&self, now: Instant) -> f32 {
91 let Some(anchor) = self.anchor else {
92 return self.time;
93 };
94 let elapsed = now.saturating_duration_since(anchor).as_secs_f32() * 1000.0 * self.speed;
95 let raw = if self.reversed {
96 self.time - elapsed
97 } else {
98 self.time + elapsed
99 };
100 raw.clamp(0.0, self.total_ms())
101 }
102
103 pub fn progress(&self) -> f32 {
106 self.clip.sample(self.time()).progress
107 }
108
109 pub fn is_playing(&self) -> bool {
112 self.anchor.is_some() && !self.is_settled_at(Instant::now())
113 }
114
115 fn is_settled_at(&self, now: Instant) -> bool {
117 if self.clip.is_endless() || self.speed <= 0.0 {
118 return false;
119 }
120 let time = self.time_at(now);
121 if self.reversed {
122 time <= 0.0
123 } else {
124 time >= self.total_ms()
125 }
126 }
127
128 pub fn frame(&self, window: &mut Window) -> Frame {
131 let now = Instant::now();
132 if self.anchor.is_some() && !self.is_settled_at(now) {
133 window.request_animation_frame();
134 }
135 self.clip.sample(self.time_at(now))
136 }
137
138 pub fn frame_at(&self, now: Instant) -> Frame {
140 self.clip.sample(self.time_at(now))
141 }
142
143 pub fn play(&mut self, cx: &mut Context<Self>) {
144 let now = Instant::now();
145 if self.anchor.is_some() && !self.is_settled_at(now) {
146 return;
147 }
148 if self.is_settled_at(now) {
151 self.time = if self.reversed { self.total_ms() } else { 0.0 };
152 }
153 self.anchor = Some(now);
154 cx.emit(AnimatorEvent::Begin);
155 self.reschedule(cx);
156 cx.notify();
157 }
158
159 pub fn pause(&mut self, cx: &mut Context<Self>) {
160 if self.anchor.is_none() {
161 return;
162 }
163 self.time = self.time_at(Instant::now());
164 self.anchor = None;
165 self.reschedule(cx);
166 cx.notify();
167 }
168
169 pub fn toggle(&mut self, cx: &mut Context<Self>) {
170 if self.is_playing() {
171 self.pause(cx);
172 } else {
173 self.play(cx);
174 }
175 }
176
177 pub fn restart(&mut self, cx: &mut Context<Self>) {
179 self.time = if self.reversed { self.total_ms() } else { 0.0 };
180 self.anchor = Some(Instant::now());
181 cx.emit(AnimatorEvent::Begin);
182 self.reschedule(cx);
183 cx.notify();
184 }
185
186 pub fn stop(&mut self, cx: &mut Context<Self>) {
188 self.time = if self.reversed { self.total_ms() } else { 0.0 };
189 self.anchor = None;
190 self.reschedule(cx);
191 cx.notify();
192 }
193
194 pub fn seek(&mut self, ms: f32, cx: &mut Context<Self>) {
196 self.time = ms.clamp(0.0, self.total_ms());
197 if self.anchor.is_some() {
198 self.anchor = Some(Instant::now());
199 }
200 self.reschedule(cx);
201 cx.notify();
202 }
203
204 pub fn seek_progress(&mut self, progress: f32, cx: &mut Context<Self>) {
207 let total = self.total_ms();
208 if total.is_finite() {
209 self.seek(total * progress.clamp(0.0, 1.0), cx);
210 }
211 }
212
213 pub fn reverse(&mut self, cx: &mut Context<Self>) {
215 self.time = self.time_at(Instant::now());
216 self.reversed = !self.reversed;
217 if self.anchor.is_some() {
218 self.anchor = Some(Instant::now());
219 }
220 self.reschedule(cx);
221 cx.notify();
222 }
223
224 pub fn is_reversed(&self) -> bool {
225 self.reversed
226 }
227
228 pub fn set_speed(&mut self, speed: f32, cx: &mut Context<Self>) {
229 self.time = self.time_at(Instant::now());
230 self.speed = speed.max(0.0);
231 if self.anchor.is_some() {
232 self.anchor = Some(Instant::now());
233 }
234 self.reschedule(cx);
235 cx.notify();
236 }
237
238 pub fn speed(&self) -> f32 {
239 self.speed
240 }
241
242 fn reschedule(&mut self, cx: &mut Context<Self>) {
248 self.epoch += 1;
249 self.completion = None;
250 if self.anchor.is_none() || self.speed <= 0.0 || self.clip.is_endless() {
251 return;
252 }
253 let remaining = if self.reversed {
254 self.time
255 } else {
256 self.total_ms() - self.time
257 };
258 if remaining <= 0.0 {
259 return;
260 }
261 let epoch = self.epoch;
262 let wait = Duration::from_secs_f32((remaining / self.speed / 1000.0).max(0.0));
263 self.completion = Some(cx.spawn(async move |this, cx| {
264 cx.background_executor().timer(wait).await;
265 this
266 .update(cx, |animator, cx| {
267 if animator.epoch != epoch {
268 return;
269 }
270 animator.time = if animator.reversed {
271 0.0
272 } else {
273 animator.total_ms()
274 };
275 animator.anchor = None;
276 animator.completion = None;
277 cx.emit(AnimatorEvent::Complete);
278 cx.notify();
279 })
280 .ok();
281 }));
282 }
283}