use crate::animations::core::Animatable;
use crate::prelude::AnimationConfig;
use std::sync::Mutex;
use std::sync::{Arc, MutexGuard};
#[derive(Clone)]
pub struct AnimationStep<T: Animatable> {
pub target: T,
pub config: Arc<AnimationConfig>,
pub predicted_next: Option<T>,
}
struct SequenceState {
current_step: u8,
#[allow(clippy::type_complexity)]
on_complete: Option<Box<dyn FnOnce() + Send>>,
}
pub struct AnimationSequence<T: Animatable> {
steps: Vec<AnimationStep<T>>,
state: Mutex<SequenceState>,
}
impl<T: Animatable> AnimationSequence<T> {
fn lock_state(&self) -> MutexGuard<'_, SequenceState> {
match self.state.lock() {
Ok(state) => state,
Err(poisoned) => poisoned.into_inner(),
}
}
pub fn new() -> Self {
Self {
steps: Vec::new(),
state: Mutex::new(SequenceState {
current_step: 0,
on_complete: None,
}),
}
}
pub fn with_capacity(capacity: u8) -> Self {
Self {
steps: Vec::with_capacity(capacity as usize),
state: Mutex::new(SequenceState {
current_step: 0,
on_complete: None,
}),
}
}
pub fn from_steps(steps: Vec<AnimationStep<T>>) -> Self {
Self {
steps,
state: Mutex::new(SequenceState {
current_step: 0,
on_complete: None,
}),
}
}
pub fn with_on_complete<F>(steps: Vec<AnimationStep<T>>, on_complete: F) -> Self
where
F: FnOnce() + Send + 'static,
{
Self {
steps,
state: Mutex::new(SequenceState {
current_step: 0,
on_complete: Some(Box::new(on_complete)),
}),
}
}
pub fn reserve(&mut self, additional: u8) {
self.steps.reserve(additional as usize);
}
pub fn then(mut self, target: T, config: AnimationConfig) -> Self {
let predicted_next = if self.steps.is_empty() {
None
} else {
self.steps
.last()
.map(|last_step| last_step.target.interpolate(&target, 0.5))
};
let new_step = AnimationStep {
target,
config: Arc::new(config),
predicted_next,
};
self.steps.push(new_step);
self
}
pub fn on_complete<F: FnOnce() + Send + 'static>(self, f: F) -> Self {
let mut state = self.lock_state();
state.on_complete = Some(Box::new(f));
drop(state);
self
}
pub fn advance_step(&self) -> bool {
let mut state = self.lock_state();
let current = state.current_step;
let total_steps = self.steps.len() as u8;
if current < total_steps.saturating_sub(1) {
state.current_step += 1;
true
} else {
false
}
}
pub fn current_step_index(&self) -> u8 {
self.lock_state().current_step
}
pub fn current_step(&self) -> u8 {
self.current_step_index()
}
pub fn current_config(&self) -> Option<&AnimationConfig> {
let current = self.current_step_index() as usize;
self.steps.get(current).map(|step| step.config.as_ref())
}
pub fn current_target(&self) -> Option<T> {
let current = self.current_step_index() as usize;
self.steps.get(current).map(|step| step.target)
}
pub fn current_step_data(&self) -> Option<&AnimationStep<T>> {
let current = self.current_step_index() as usize;
self.steps.get(current)
}
pub fn steps(&self) -> &[AnimationStep<T>] {
&self.steps
}
pub fn is_complete(&self) -> bool {
let current = self.current_step_index();
let total_steps = self.steps.len() as u8;
current >= total_steps.saturating_sub(1)
}
pub fn total_steps(&self) -> usize {
self.steps.len()
}
pub fn reset(&self) {
self.lock_state().current_step = 0;
}
pub fn execute_completion(&self) {
if let Some(callback) = self.lock_state().on_complete.take() {
callback();
}
}
}
impl<T: Animatable> Clone for AnimationSequence<T> {
fn clone(&self) -> Self {
let current_step = self.current_step_index();
Self {
steps: self.steps.clone(),
state: Mutex::new(SequenceState {
current_step,
on_complete: None,
}),
}
}
}
impl<T: Animatable> Default for AnimationSequence<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::animations::core::AnimationMode;
use crate::animations::spring::Spring;
use std::sync::{Arc, Mutex};
#[test]
fn test_animation_sequence_basic() {
let steps = vec![
AnimationStep {
target: 10.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
},
AnimationStep {
target: 20.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
},
AnimationStep {
target: 30.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
},
];
let sequence = AnimationSequence::from_steps(steps);
assert_eq!(sequence.current_step_index(), 0);
assert_eq!(sequence.current_target().unwrap(), 10.0f32);
assert!(!sequence.is_complete());
assert_eq!(sequence.total_steps(), 3);
assert!(sequence.advance_step());
assert_eq!(sequence.current_step_index(), 1);
assert_eq!(sequence.current_target().unwrap(), 20.0f32);
assert!(!sequence.is_complete());
assert!(sequence.advance_step());
assert_eq!(sequence.current_step_index(), 2);
assert_eq!(sequence.current_target().unwrap(), 30.0f32);
assert!(sequence.is_complete());
assert!(!sequence.advance_step());
assert_eq!(sequence.current_step_index(), 2);
sequence.reset();
assert_eq!(sequence.current_step_index(), 0);
assert!(!sequence.is_complete());
}
#[test]
fn test_animation_sequence_builder_pattern() {
let sequence = AnimationSequence::new()
.then(
10.0f32,
AnimationConfig::new(AnimationMode::Spring(Spring::default())),
)
.then(
20.0f32,
AnimationConfig::new(AnimationMode::Spring(Spring::default())),
)
.then(
30.0f32,
AnimationConfig::new(AnimationMode::Spring(Spring::default())),
);
assert_eq!(sequence.total_steps(), 3);
assert_eq!(sequence.current_target().unwrap(), 10.0f32);
assert!(!sequence.is_complete());
assert!(sequence.advance_step());
assert_eq!(sequence.current_target().unwrap(), 20.0f32);
assert!(sequence.advance_step());
assert_eq!(sequence.current_target().unwrap(), 30.0f32);
assert!(sequence.is_complete());
}
#[test]
fn test_animation_sequence_with_callback() {
let callback_executed = Arc::new(Mutex::new(false));
let callback_executed_clone = callback_executed.clone();
let steps = vec![AnimationStep {
target: 10.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
}];
let sequence = AnimationSequence::with_on_complete(steps, move || {
*callback_executed_clone.lock().unwrap() = true;
});
sequence.execute_completion();
assert!(*callback_executed.lock().unwrap());
}
#[test]
fn test_animation_sequence_callback_with_shared_references() {
let callback_executed = Arc::new(Mutex::new(false));
let callback_executed_clone = callback_executed.clone();
let steps = vec![AnimationStep {
target: 10.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
}];
let sequence = AnimationSequence::with_on_complete(steps, move || {
*callback_executed_clone.lock().unwrap() = true;
});
let sequence_arc1 = Arc::new(sequence);
let sequence_arc2 = sequence_arc1.clone();
let sequence_arc3 = sequence_arc1.clone();
assert!(Arc::try_unwrap(sequence_arc1.clone()).is_err());
sequence_arc1.execute_completion();
assert!(*callback_executed.lock().unwrap());
assert_eq!(sequence_arc2.current_step_index(), 0);
assert_eq!(sequence_arc3.current_step_index(), 0);
}
#[test]
fn test_animation_sequence_clone() {
let steps = vec![AnimationStep {
target: 10.0f32,
config: Arc::new(AnimationConfig::new(AnimationMode::Spring(
Spring::default(),
))),
predicted_next: None,
}];
let sequence1 = AnimationSequence::from_steps(steps);
sequence1.advance_step();
let sequence2 = sequence1.clone();
assert_eq!(
sequence1.current_step_index(),
sequence2.current_step_index()
);
assert_eq!(sequence1.total_steps(), sequence2.total_steps());
assert_eq!(sequence1.current_target(), sequence2.current_target());
}
#[test]
fn test_animation_sequence_backward_compatibility() {
let sequence = AnimationSequence::new();
let sequence = sequence.then(
10.0f32,
AnimationConfig::new(AnimationMode::Spring(Spring::default())),
);
let sequence = sequence.then(
20.0f32,
AnimationConfig::new(AnimationMode::Spring(Spring::default())),
);
assert_eq!(sequence.current_step(), 0);
assert_eq!(sequence.steps().len(), 2);
let _sequence_with_capacity = AnimationSequence::<f32>::with_capacity(10);
let mut sequence_mut = sequence.clone();
sequence_mut.reserve(5);
}
}