use std::collections::HashMap;
use std::rc::Rc;
use tairitsu_vdom::Platform;
use super::builder::AnimationBuilder;
use super::state::AnimationDataStore as AnimationState;
pub type ElementHandle = u64;
pub enum LifecycleCallback {
OnComplete(Box<dyn FnOnce()>),
OnInterrupt(Box<dyn FnOnce()>),
OnError(Box<dyn FnOnce()>),
}
struct AnimationEntry {
stop_fn: Box<dyn FnOnce()>,
cleanup_fn: Option<Box<dyn FnOnce()>>,
callbacks: Vec<LifecycleCallback>,
_target_element: Option<ElementHandle>, created_at: std::time::Instant,
}
pub struct AnimationRegistry {
animations: HashMap<String, AnimationEntry>,
next_id: u64,
default_timeout_ms: Option<u64>,
}
impl AnimationRegistry {
pub fn new() -> Self {
Self {
animations: HashMap::new(),
next_id: 0,
default_timeout_ms: None,
}
}
pub fn new_with_timeout(timeout_ms: Option<u64>) -> Self {
Self {
animations: HashMap::new(),
next_id: 0,
default_timeout_ms: timeout_ms,
}
}
pub fn register_animation(
&mut self,
stop_fn: Box<dyn FnOnce()>,
cleanup_fn: Option<Box<dyn FnOnce()>>,
target_element: Option<ElementHandle>,
) -> String {
let id = format!("animation_{}", self.next_id);
self.next_id += 1;
self.animations.insert(
id.clone(),
AnimationEntry {
stop_fn,
cleanup_fn,
callbacks: Vec::new(),
_target_element: target_element,
created_at: std::time::Instant::now(),
},
);
id
}
pub fn register_with_callbacks(
&mut self,
stop_fn: Box<dyn FnOnce()>,
callbacks: Vec<LifecycleCallback>,
target_element: Option<ElementHandle>,
) -> String {
let id = format!("animation_{}", self.next_id);
self.next_id += 1;
self.animations.insert(
id.clone(),
AnimationEntry {
stop_fn,
cleanup_fn: None,
callbacks,
_target_element: target_element,
created_at: std::time::Instant::now(),
},
);
id
}
fn is_target_valid(&self, _entry: &AnimationEntry) -> bool {
true
}
pub fn stop_animation(&mut self, id: &str, reason: &str) -> bool {
if let Some(entry) = self.animations.remove(id) {
let callbacks_to_call: Vec<_> = entry
.callbacks
.into_iter()
.filter_map(|cb| match cb {
LifecycleCallback::OnInterrupt(f) => Some(f),
_ => None,
})
.collect();
(entry.stop_fn)();
if let Some(cleanup) = entry.cleanup_fn {
cleanup();
}
for cb in callbacks_to_call {
cb();
}
eprintln!("🛑 Animation {} stopped: {}", id, reason);
true
} else {
false
}
}
pub fn stop_all(&mut self, reason: &str) {
let animations: Vec<(String, AnimationEntry)> = self.animations.drain().collect();
for (id, entry) in animations {
let callbacks_to_call: Vec<_> = entry
.callbacks
.into_iter()
.filter_map(|cb| match cb {
LifecycleCallback::OnInterrupt(f) => Some(f),
_ => None,
})
.collect();
(entry.stop_fn)();
if let Some(cleanup) = entry.cleanup_fn {
cleanup();
}
for cb in callbacks_to_call {
cb();
}
eprintln!("🛑 Animation {} stopped: {}", id, reason);
}
}
pub fn cleanup_invalid(&mut self) -> usize {
let mut to_remove: Vec<String> = Vec::new();
for (id, entry) in &self.animations {
if !self.is_target_valid(entry) {
to_remove.push(id.clone());
}
}
for id in &to_remove {
self.stop_animation(id, "target element removed");
}
if !to_remove.is_empty() {
eprintln!("🧹 Cleaned up {} invalid animations", to_remove.len());
}
to_remove.len()
}
pub fn cleanup_timed_out(&mut self) -> usize {
if let Some(timeout_ms) = self.default_timeout_ms {
let timeout = std::time::Duration::from_millis(timeout_ms);
let mut to_remove: Vec<String> = Vec::new();
let now = std::time::Instant::now();
for (id, entry) in &self.animations {
if now.duration_since(entry.created_at) > timeout {
to_remove.push(id.clone());
}
}
for id in &to_remove {
self.stop_animation(id, "timeout exceeded");
}
to_remove.len()
} else {
0
}
}
pub fn active_count(&self) -> usize {
self.animations.len()
}
pub fn has_active(&self) -> bool {
!self.animations.is_empty()
}
pub fn active_ids(&self) -> Vec<String> {
self.animations.keys().cloned().collect()
}
pub fn get_animation_info(&self, id: &str) -> Option<(std::time::Duration, usize)> {
self.animations.get(id).map(|entry| {
let duration = std::time::Instant::now().duration_since(entry.created_at);
let callback_count = entry.callbacks.len();
(duration, callback_count)
})
}
}
impl Default for AnimationRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct AnimationManager {
registry: AnimationRegistry,
component_ids: Vec<String>,
}
impl AnimationManager {
pub fn new() -> Self {
Self {
registry: AnimationRegistry::new(),
component_ids: Vec::new(),
}
}
pub fn new_with_timeout(timeout_ms: Option<u64>) -> Self {
Self {
registry: AnimationRegistry::new_with_timeout(timeout_ms),
component_ids: Vec::new(),
}
}
pub fn start_animation<P: Platform>(
&mut self,
builder: AnimationBuilder<P>,
cleanup_fn: Option<Box<dyn FnOnce()>>,
target_element: Option<ElementHandle>,
) -> String {
let stop_fn = builder.start_continuous_animation();
let id = self
.registry
.register_animation(stop_fn, cleanup_fn, target_element);
self.component_ids.push(id.clone());
id
}
pub fn start_animation_with_state<P: Platform>(
&mut self,
_platform: Rc<std::cell::RefCell<P>>,
_elements: &HashMap<String, ElementHandle>,
_initial_state: AnimationState,
_cleanup_fn: Option<Box<dyn FnOnce()>>,
_target_element: Option<ElementHandle>,
) -> String {
let id = format!("manual_{}", self.component_ids.len());
self.component_ids.push(id.clone());
id
}
pub fn stop_animation(&mut self, id: &str) -> bool {
let stopped = self.registry.stop_animation(id, "manual stop");
if stopped {
self.component_ids.retain(|tracked_id| tracked_id != id);
}
stopped
}
pub fn cleanup(&mut self) {
let ids = self.component_ids.clone();
for id in ids {
self.registry.stop_animation(&id, "manager cleanup");
}
self.component_ids.clear();
}
pub fn cleanup_invalid(&mut self) -> usize {
self.registry.cleanup_invalid()
}
pub fn cleanup_timed_out(&mut self) -> usize {
self.registry.cleanup_timed_out()
}
pub fn managed_count(&self) -> usize {
self.component_ids.len()
}
pub fn managed_ids(&self) -> Vec<String> {
self.component_ids.clone()
}
pub fn get_animation_info(&self, id: &str) -> Option<(std::time::Duration, usize)> {
self.registry.get_animation_info(id)
}
}
impl Default for AnimationManager {
fn default() -> Self {
Self::new()
}
}
impl Drop for AnimationManager {
fn drop(&mut self) {
self.cleanup();
}
}