use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use tairitsu_vdom::Platform;
use super::super::context::AnimationContext;
use super::super::state::AnimationDataStore as StructAnimationState;
use super::super::style::CssProperty;
use super::action::AnimationAction;
use super::value::DynamicValue;
pub struct AnimationBuilder<'a, P: Platform> {
platform: Rc<RefCell<P>>,
elements: &'a HashMap<String, P::Element>,
actions: HashMap<String, Vec<AnimationAction<P>>>,
initial_state: StructAnimationState,
}
impl<'a, P: Platform> AnimationBuilder<'a, P> {
pub fn new(platform: Rc<RefCell<P>>, elements: &'a HashMap<String, P::Element>) -> Self {
Self {
platform,
elements,
actions: HashMap::new(),
initial_state: StructAnimationState::new(),
}
}
pub fn new_with_state(
platform: Rc<RefCell<P>>,
elements: &'a HashMap<String, P::Element>,
initial_state: StructAnimationState,
) -> Self {
Self {
platform,
elements,
actions: HashMap::new(),
initial_state,
}
}
pub fn add_style(
mut self,
element_name: &str,
property: CssProperty,
value: impl Into<String>,
) -> Self {
self.actions
.entry(element_name.to_string())
.or_default()
.push(AnimationAction::style_static(property, value));
self
}
pub fn add_style_dynamic<F>(mut self, element_name: &str, property: CssProperty, f: F) -> Self
where
F: Fn(&AnimationContext<P>) -> String + 'static,
{
self.actions
.entry(element_name.to_string())
.or_default()
.push(AnimationAction::style_dynamic(property, f));
self
}
pub fn add_stateful_style<F>(mut self, element_name: &str, property: CssProperty, f: F) -> Self
where
F: Fn(&AnimationContext<P>, &mut StructAnimationState) -> String + 'static,
{
self.actions
.entry(element_name.to_string())
.or_default()
.push(AnimationAction::style_stateful_dynamic(property, f));
self
}
pub fn add_class(mut self, element_name: &str, class: impl Into<String>) -> Self {
self.actions
.entry(element_name.to_string())
.or_default()
.push(AnimationAction::class(class));
self
}
pub fn add_classes(mut self, element_name: &str, classes: &[impl AsRef<str>]) -> Self {
for class in classes {
self.actions
.entry(element_name.to_string())
.or_default()
.push(AnimationAction::class(class.as_ref()));
}
self
}
pub fn apply(self) {
self.apply_internal(false)
}
pub fn apply_with_transition(self, duration: &str, easing: &str) {
self.apply_with_transition_internal(duration, easing, false)
}
pub fn start_continuous_animation(self) -> Box<dyn FnOnce()> {
self.start_animation_loop()
}
pub fn start_animation_loop(self) -> Box<dyn FnOnce()> {
let platform = Rc::clone(&self.platform);
let platform_for_stop = platform.clone();
let elements = self.elements.clone();
let actions = self.actions;
let mut state = self.initial_state;
let should_stop = Rc::new(RefCell::new(false));
let should_stop_clone = should_stop.clone();
let timing = Rc::new(RefCell::new((0.0, 0.0, 0.0)));
let cached_values: Rc<RefCell<HashMap<String, HashMap<CssProperty, String>>>> =
Rc::new(RefCell::new(HashMap::new()));
let raf_id = Rc::new(RefCell::new(None::<u32>));
let initial_callback = Box::new({
let platform = platform.clone();
let elements = elements.clone();
let actions = actions.clone();
let should_stop = should_stop.clone();
let timing = timing.clone();
let cached_values = cached_values.clone();
let raf_id = raf_id.clone();
move |timestamp: f64| {
if *should_stop.borrow() {
return;
}
let current_time = timestamp;
let mut timing_ref = timing.borrow_mut();
timing_ref.1 = current_time;
timing_ref.2 = current_time;
drop(timing_ref);
let mut cached_ref = cached_values.borrow_mut();
let mut needs_update = false;
for (element_name, element_handle) in &elements {
if let Some(element_actions) = actions.get(element_name) {
let ctx = AnimationContext::new_with_timing(
platform.clone(),
element_handle.clone(),
0.0,
current_time,
);
let mut new_styles: Vec<(CssProperty, String)> = Vec::new();
let element_cache = cached_ref.entry(element_name.clone()).or_default();
for action in element_actions {
if let AnimationAction::Style(prop, value) = action
&& matches!(
value,
DynamicValue::Dynamic(_) | DynamicValue::StatefulDynamic(_)
)
{
let new_value = value.evaluate(&ctx, &mut state);
if let Some(old_value) = element_cache.get(prop) {
if old_value != &new_value {
new_styles.push((*prop, new_value.clone()));
element_cache.insert(*prop, new_value);
needs_update = true;
}
} else {
new_styles.push((*prop, new_value.clone()));
element_cache.insert(*prop, new_value);
needs_update = true;
}
}
}
if needs_update && !new_styles.is_empty() {
for (prop, value_str) in &new_styles {
platform.borrow_mut().set_style(
element_handle,
prop.as_str(),
value_str,
);
}
needs_update = false;
}
}
}
drop(cached_ref);
if !*should_stop.borrow() {
let platform_clone = platform.clone();
let should_stop = should_stop.clone();
let callback = Box::new(move |_ts: f64| {
if !*should_stop.borrow() {
let platform = platform_clone.clone();
let _should_stop = should_stop.clone();
let inner_callback = Box::new(move |_inner_ts: f64| {
});
let id = platform
.borrow_mut()
.request_animation_frame(inner_callback);
let _ = id;
}
});
let id = platform.borrow_mut().request_animation_frame(callback);
*raf_id.borrow_mut() = Some(id);
}
}
});
let id = platform
.borrow_mut()
.request_animation_frame(initial_callback);
*raf_id.borrow_mut() = Some(id);
Box::new(move || {
*should_stop_clone.borrow_mut() = true;
if let Some(id) = raf_id.take() {
platform_for_stop.borrow_mut().cancel_animation_frame(id);
}
})
}
fn apply_internal(self, _is_transition: bool) {
for (element_name, actions) in self.actions {
if let Some(element_handle) = self.elements.get(&element_name) {
let ctx = AnimationContext::new(self.platform.clone(), element_handle.clone());
let mut state = self.initial_state.clone();
for action in &actions {
match action {
AnimationAction::Style(prop, value) => {
let value_str = value.evaluate(&ctx, &mut state);
self.platform.borrow_mut().set_style(
element_handle,
prop.as_str(),
&value_str,
);
}
AnimationAction::Class(class) => {
self.platform.borrow_mut().set_attribute(
element_handle,
"class",
class,
);
}
}
}
}
}
}
fn apply_with_transition_internal(self, duration: &str, easing: &str, _is_transition: bool) {
for element_name in self.actions.keys() {
if let Some(element_handle) = self.elements.get(element_name) {
self.platform.borrow_mut().set_style(
element_handle,
"transition",
&format!("all {} {}", duration, easing),
);
}
}
self.apply();
}
}
pub fn new_animation_builder<'a, P: Platform>(
platform: Rc<RefCell<P>>,
elements: &'a HashMap<String, P::Element>,
) -> AnimationBuilder<'a, P> {
AnimationBuilder::new(platform, elements)
}