use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use super::easing;
use super::interpolation::Interpolate;
use crate::data::observable::{Observable, SubscriberId};
#[allow(clippy::type_complexity)]
pub struct AnimatedObservable<T> {
current: Observable<T>,
target: Arc<RwLock<Option<T>>>,
start_value: Arc<RwLock<Option<T>>>,
progress: Arc<RwLock<f64>>,
duration_secs: Arc<RwLock<f64>>,
easing_fn: Arc<RwLock<fn(f64) -> f64>>,
animating: Arc<AtomicBool>,
}
impl<T: Clone> Clone for AnimatedObservable<T> {
fn clone(&self) -> Self {
Self {
current: self.current.clone(),
target: Arc::clone(&self.target),
start_value: Arc::clone(&self.start_value),
progress: Arc::clone(&self.progress),
duration_secs: Arc::clone(&self.duration_secs),
easing_fn: Arc::clone(&self.easing_fn),
animating: Arc::clone(&self.animating),
}
}
}
impl<T: Interpolate + Clone + Send + Sync + 'static> AnimatedObservable<T> {
pub fn new(value: T) -> Self {
Self {
current: Observable::new(value),
target: Arc::new(RwLock::new(None)),
start_value: Arc::new(RwLock::new(None)),
progress: Arc::new(RwLock::new(0.0)),
duration_secs: Arc::new(RwLock::new(0.0)),
easing_fn: Arc::new(RwLock::new(easing::ease_in_out_quad)),
animating: Arc::new(AtomicBool::new(false)),
}
}
pub fn animate_to(&self, target: T, duration_ms: u64) {
self.animate_to_with_easing(target, duration_ms, easing::ease_in_out_quad);
}
pub fn animate_to_with_easing(&self, target: T, duration_ms: u64, easing: fn(f64) -> f64) {
{
let mut start = self.start_value.write().expect("Lock poisoned");
*start = Some(self.current.get());
}
{
let mut tgt = self.target.write().expect("Lock poisoned");
*tgt = Some(target);
}
{
let mut prog = self.progress.write().expect("Lock poisoned");
*prog = 0.0;
}
{
let mut dur = self.duration_secs.write().expect("Lock poisoned");
*dur = duration_ms as f64 / 1000.0;
}
{
let mut ease = self.easing_fn.write().expect("Lock poisoned");
*ease = easing;
}
self.animating.store(true, Ordering::Release);
}
pub fn tick(&self, delta_time: f64) -> bool {
if !self.animating.load(Ordering::Acquire) {
return false;
}
let duration = *self.duration_secs.read().expect("Lock poisoned");
if duration <= 0.0 {
self.animating.store(false, Ordering::Release);
return false;
}
let new_progress = {
let mut prog = self.progress.write().expect("Lock poisoned");
*prog += delta_time / duration;
if *prog >= 1.0 {
*prog = 1.0;
}
*prog
};
let easing_fn = *self.easing_fn.read().expect("Lock poisoned");
let eased_t = easing_fn(new_progress);
let start = self.start_value.read().expect("Lock poisoned");
let target = self.target.read().expect("Lock poisoned");
if let (Some(start_val), Some(target_val)) = (&*start, &*target) {
let interpolated = start_val.interpolate(target_val, eased_t);
self.current.set(interpolated);
}
if new_progress >= 1.0 {
self.animating.store(false, Ordering::Release);
return false;
}
true
}
pub fn get(&self) -> T {
self.current.get()
}
pub fn set_immediate(&self, value: T) {
self.stop();
self.current.set(value);
}
pub fn is_animating(&self) -> bool {
self.animating.load(Ordering::Acquire)
}
pub fn stop(&self) {
self.animating.store(false, Ordering::Release);
}
pub fn progress(&self) -> f64 {
*self.progress.read().expect("Lock poisoned")
}
pub fn subscribe<F>(&self, callback: F) -> SubscriberId
where
F: Fn() + Send + Sync + 'static,
{
self.current.subscribe(callback)
}
pub fn unsubscribe(&self, id: SubscriberId) -> bool {
self.current.unsubscribe(id)
}
pub fn as_observable(&self) -> &Observable<T> {
&self.current
}
pub fn version(&self) -> u64 {
self.current.version()
}
}
impl<T: Interpolate + Clone + Send + Sync + Default + 'static> Default for AnimatedObservable<T> {
fn default() -> Self {
Self::new(T::default())
}
}
pub struct AnimationGroup<'a> {
animations: Vec<&'a dyn Tickable>,
}
pub trait Tickable {
fn tick(&self, delta_time: f64) -> bool;
fn is_animating(&self) -> bool;
}
impl<T: Interpolate + Clone + Send + Sync + 'static> Tickable for AnimatedObservable<T> {
fn tick(&self, delta_time: f64) -> bool {
AnimatedObservable::tick(self, delta_time)
}
fn is_animating(&self) -> bool {
AnimatedObservable::is_animating(self)
}
}
impl<'a> AnimationGroup<'a> {
pub fn new() -> Self {
Self {
animations: Vec::new(),
}
}
pub fn add<T: Interpolate + Clone + Send + Sync + 'static>(
&mut self,
animation: &'a AnimatedObservable<T>,
) {
self.animations.push(animation);
}
pub fn tick(&self, delta_time: f64) -> bool {
let mut any_animating = false;
for anim in &self.animations {
if anim.tick(delta_time) {
any_animating = true;
}
}
any_animating
}
pub fn is_animating(&self) -> bool {
self.animations.iter().any(|a| a.is_animating())
}
pub fn len(&self) -> usize {
self.animations.len()
}
pub fn is_empty(&self) -> bool {
self.animations.is_empty()
}
}
impl<'a> Default for AnimationGroup<'a> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_animated_observable_new() {
let anim = AnimatedObservable::new(0.0);
assert_eq!(anim.get(), 0.0);
assert!(!anim.is_animating());
}
#[test]
fn test_animated_observable_set_immediate() {
let anim = AnimatedObservable::new(0.0);
anim.set_immediate(100.0);
assert_eq!(anim.get(), 100.0);
assert!(!anim.is_animating());
}
#[test]
fn test_animated_observable_animate_to() {
let anim = AnimatedObservable::new(0.0);
anim.animate_to(100.0, 1000);
assert!(anim.is_animating());
assert_eq!(anim.get(), 0.0);
anim.tick(0.5);
let mid_value = anim.get();
assert!(mid_value > 0.0 && mid_value < 100.0);
assert!(anim.is_animating());
anim.tick(0.6);
assert_eq!(anim.get(), 100.0);
assert!(!anim.is_animating());
}
#[test]
fn test_animated_observable_linear_easing() {
let anim = AnimatedObservable::new(0.0);
anim.animate_to_with_easing(100.0, 1000, easing::linear);
anim.tick(0.5);
let mid_value = anim.get();
assert!((mid_value - 50.0_f64).abs() < 0.01);
}
#[test]
fn test_animated_observable_stop() {
let anim = AnimatedObservable::new(0.0);
anim.animate_to(100.0, 1000);
anim.tick(0.5);
let mid_value = anim.get();
anim.stop();
assert!(!anim.is_animating());
assert_eq!(anim.get(), mid_value);
}
#[test]
fn test_animated_observable_vec() {
let anim = AnimatedObservable::new(vec![0.0, 0.0, 0.0]);
anim.animate_to_with_easing(vec![100.0, 200.0, 300.0], 1000, easing::linear);
anim.tick(0.5);
let mid = anim.get();
assert!((mid[0] - 50.0_f64).abs() < 0.01);
assert!((mid[1] - 100.0_f64).abs() < 0.01);
assert!((mid[2] - 150.0_f64).abs() < 0.01);
anim.tick(0.6);
let end = anim.get();
assert_eq!(end, vec![100.0, 200.0, 300.0]);
}
#[test]
fn test_animated_observable_progress() {
let anim = AnimatedObservable::new(0.0);
anim.animate_to(100.0, 1000);
assert!((anim.progress() - 0.0).abs() < 0.001);
anim.tick(0.25);
assert!((anim.progress() - 0.25).abs() < 0.001);
anim.tick(0.75);
assert!((anim.progress() - 1.0).abs() < 0.001);
}
#[test]
fn test_animated_observable_version_changes() {
let anim = AnimatedObservable::new(0.0);
let v0 = anim.version();
anim.animate_to(100.0, 1000);
anim.tick(0.5);
let v1 = anim.version();
assert!(v1 > v0);
anim.tick(0.6);
let v2 = anim.version();
assert!(v2 > v1);
}
#[test]
fn test_animation_group() {
let x = AnimatedObservable::new(0.0);
let y = AnimatedObservable::new(0.0);
let mut group = AnimationGroup::new();
group.add(&x);
group.add(&y);
assert_eq!(group.len(), 2);
assert!(!group.is_animating());
x.animate_to(100.0, 1000);
y.animate_to(200.0, 500);
assert!(group.is_animating());
assert!(group.tick(0.3));
assert!(x.is_animating());
assert!(y.is_animating());
group.tick(0.3);
assert!(x.is_animating());
assert!(!y.is_animating());
assert_eq!(y.get(), 200.0);
group.tick(0.5);
assert!(!x.is_animating());
assert!(!group.is_animating());
}
#[test]
fn test_animated_observable_subscribe() {
let anim = AnimatedObservable::new(0.0);
let change_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let count_clone = std::sync::Arc::clone(&change_count);
anim.subscribe(move || {
count_clone.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
});
anim.animate_to(100.0, 1000);
anim.tick(0.5);
anim.tick(0.6);
assert!(change_count.load(std::sync::atomic::Ordering::Relaxed) >= 2);
}
#[test]
fn test_animated_observable_zero_duration() {
let anim = AnimatedObservable::new(0.0);
anim.animate_to(100.0, 0);
anim.tick(0.016);
assert!(!anim.is_animating());
}
}