use std::time::{Duration, Instant};
use gpui::{Context, EventEmitter, Task, Window};
use super::{Clip, Frame};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnimatorEvent {
Begin,
Complete,
}
pub struct Animator {
clip: Clip,
time: f32,
anchor: Option<Instant>,
speed: f32,
reversed: bool,
epoch: usize,
completion: Option<Task<()>>,
}
impl EventEmitter<AnimatorEvent> for Animator {}
impl Animator {
pub fn new(clip: impl Into<Clip>, _cx: &mut Context<Self>) -> Self {
Animator {
clip: clip.into(),
time: 0.0,
anchor: None,
speed: 1.0,
reversed: false,
epoch: 0,
completion: None,
}
}
pub fn autoplay(mut self, cx: &mut Context<Self>) -> Self {
self.play(cx);
self
}
pub fn reversed(mut self, reversed: bool) -> Self {
self.reversed = reversed;
self.time = if reversed { self.total_ms() } else { 0.0 };
self
}
pub fn clip(&self) -> &Clip {
&self.clip
}
fn total_ms(&self) -> f32 {
self.clip.total_ms()
}
pub fn time(&self) -> f32 {
self.time_at(Instant::now())
}
pub fn time_at(&self, now: Instant) -> f32 {
let Some(anchor) = self.anchor else {
return self.time;
};
let elapsed = now.saturating_duration_since(anchor).as_secs_f32() * 1000.0 * self.speed;
let raw = if self.reversed {
self.time - elapsed
} else {
self.time + elapsed
};
raw.clamp(0.0, self.total_ms())
}
pub fn progress(&self) -> f32 {
self.clip.sample(self.time()).progress
}
pub fn is_playing(&self) -> bool {
self.anchor.is_some() && !self.is_settled_at(Instant::now())
}
fn is_settled_at(&self, now: Instant) -> bool {
if self.clip.is_endless() || self.speed <= 0.0 {
return false;
}
let time = self.time_at(now);
if self.reversed {
time <= 0.0
} else {
time >= self.total_ms()
}
}
pub fn frame(&self, window: &mut Window) -> Frame {
let now = Instant::now();
if self.anchor.is_some() && !self.is_settled_at(now) {
window.request_animation_frame();
}
self.clip.sample(self.time_at(now))
}
pub fn frame_at(&self, now: Instant) -> Frame {
self.clip.sample(self.time_at(now))
}
pub fn play(&mut self, cx: &mut Context<Self>) {
let now = Instant::now();
if self.anchor.is_some() && !self.is_settled_at(now) {
return;
}
if self.is_settled_at(now) {
self.time = if self.reversed { self.total_ms() } else { 0.0 };
}
self.anchor = Some(now);
cx.emit(AnimatorEvent::Begin);
self.reschedule(cx);
cx.notify();
}
pub fn pause(&mut self, cx: &mut Context<Self>) {
if self.anchor.is_none() {
return;
}
self.time = self.time_at(Instant::now());
self.anchor = None;
self.reschedule(cx);
cx.notify();
}
pub fn toggle(&mut self, cx: &mut Context<Self>) {
if self.is_playing() {
self.pause(cx);
} else {
self.play(cx);
}
}
pub fn restart(&mut self, cx: &mut Context<Self>) {
self.time = if self.reversed { self.total_ms() } else { 0.0 };
self.anchor = Some(Instant::now());
cx.emit(AnimatorEvent::Begin);
self.reschedule(cx);
cx.notify();
}
pub fn stop(&mut self, cx: &mut Context<Self>) {
self.time = if self.reversed { self.total_ms() } else { 0.0 };
self.anchor = None;
self.reschedule(cx);
cx.notify();
}
pub fn seek(&mut self, ms: f32, cx: &mut Context<Self>) {
self.time = ms.clamp(0.0, self.total_ms());
if self.anchor.is_some() {
self.anchor = Some(Instant::now());
}
self.reschedule(cx);
cx.notify();
}
pub fn seek_progress(&mut self, progress: f32, cx: &mut Context<Self>) {
let total = self.total_ms();
if total.is_finite() {
self.seek(total * progress.clamp(0.0, 1.0), cx);
}
}
pub fn reverse(&mut self, cx: &mut Context<Self>) {
self.time = self.time_at(Instant::now());
self.reversed = !self.reversed;
if self.anchor.is_some() {
self.anchor = Some(Instant::now());
}
self.reschedule(cx);
cx.notify();
}
pub fn is_reversed(&self) -> bool {
self.reversed
}
pub fn set_speed(&mut self, speed: f32, cx: &mut Context<Self>) {
self.time = self.time_at(Instant::now());
self.speed = speed.max(0.0);
if self.anchor.is_some() {
self.anchor = Some(Instant::now());
}
self.reschedule(cx);
cx.notify();
}
pub fn speed(&self) -> f32 {
self.speed
}
fn reschedule(&mut self, cx: &mut Context<Self>) {
self.epoch += 1;
self.completion = None;
if self.anchor.is_none() || self.speed <= 0.0 || self.clip.is_endless() {
return;
}
let remaining = if self.reversed {
self.time
} else {
self.total_ms() - self.time
};
if remaining <= 0.0 {
return;
}
let epoch = self.epoch;
let wait = Duration::from_secs_f32((remaining / self.speed / 1000.0).max(0.0));
self.completion = Some(cx.spawn(async move |this, cx| {
cx.background_executor().timer(wait).await;
this.update(cx, |animator, cx| {
if animator.epoch != epoch {
return;
}
animator.time = if animator.reversed {
0.0
} else {
animator.total_ms()
};
animator.anchor = None;
animator.completion = None;
cx.emit(AnimatorEvent::Complete);
cx.notify();
})
.ok();
}));
}
}