use crate::{
ActivePropertyTransition, AnimationHandle, AnimationRuntime, AnimationTargetId, BehaviorRule,
Duration, PropertyTransitionProgress, PropertyTransitionRegistration, Timing,
behavior::TransitionValueKind, property::PropertySpec, runtime::AnimationClock,
};
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyTransition<K: TransitionValueKind>
where
K::Inner: Copy + PartialEq,
{
target: AnimationTargetId,
property: PropertySpec<K>,
timing: Timing,
current: Option<K::Inner>,
active: Option<ActivePropertyTransition<K>>,
}
impl<K> PropertyTransition<K>
where
K: TransitionValueKind,
K::Inner: Copy + PartialEq,
{
#[must_use]
pub fn new(target: AnimationTargetId, property: PropertySpec<K>) -> Self {
Self::from_rule(target, &BehaviorRule::new(property))
}
#[must_use]
pub const fn from_rule(target: AnimationTargetId, rule: &BehaviorRule<K>) -> Self {
Self {
target,
property: rule.property(),
timing: rule.timing(),
current: None,
active: None,
}
}
#[must_use]
pub const fn with_timing(mut self, timing: Timing) -> Self {
self.timing = timing;
self
}
#[must_use]
pub const fn target(&self) -> AnimationTargetId {
self.target
}
#[must_use]
pub const fn property(&self) -> PropertySpec<K> {
self.property
}
#[must_use]
pub const fn timing(&self) -> Timing {
self.timing
}
#[must_use]
pub const fn current_value(&self) -> Option<K::Inner> {
self.current
}
#[must_use]
pub const fn active_handle(&self) -> Option<AnimationHandle> {
match &self.active {
Some(active) => Some(active.handle()),
None => None,
}
}
#[must_use]
pub fn is_active<C: AnimationClock>(&self, runtime: &AnimationRuntime<C>) -> bool {
match &self.active {
Some(active) => runtime.contains(self.target, active.handle()),
None => false,
}
}
pub fn handle_completion<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) -> bool {
let Some(active) = &self.active else {
return false;
};
if runtime.contains(self.target, active.handle()) {
return false;
}
self.active = None;
true
}
#[must_use]
pub const fn active_transition(&self) -> Option<&ActivePropertyTransition<K>> {
self.active.as_ref()
}
#[must_use]
pub fn active_progress_at(&self, timestamp: Duration) -> Option<PropertyTransitionProgress<K>> {
self.active
.as_ref()
.map(|active| active.progress_at(timestamp))
}
pub fn transition_to<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
value: K::Inner,
) -> Option<PropertyTransitionRegistration> {
self.invalidate_if_stale(runtime);
let Some(previous) = self.current else {
self.current = Some(value);
return None;
};
if previous == value {
return None;
}
let from = self.current_visual_value(runtime).unwrap_or(previous);
Some(self.register_from(runtime, from, value))
}
pub fn transition_from_visual<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
visual: K::Inner,
value: K::Inner,
) -> Option<PropertyTransitionRegistration> {
self.invalidate_if_stale(runtime);
if self.current == Some(value) {
return None;
}
self.current = Some(value);
if visual == value {
return None;
}
Some(self.register_from(runtime, visual, value))
}
pub fn retarget_to<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
value: K::Inner,
) -> Option<PropertyTransitionRegistration> {
self.invalidate_if_stale(runtime);
if self.current == Some(value) {
return None;
}
let from = self.current_visual_value(runtime)?;
Some(self.register_from(runtime, from, value))
}
pub fn interrupt_from_visual<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
visual: K::Inner,
value: K::Inner,
) -> Option<PropertyTransitionRegistration> {
self.invalidate_if_stale(runtime);
if self.active.is_none() && self.current == Some(value) {
return None;
}
self.current = Some(value);
if visual == value {
if let Some(active) = self.active.take() {
runtime.cancel(self.target, active.handle());
}
return None;
}
Some(self.register_from(runtime, visual, value))
}
fn register_from<C: AnimationClock>(
&mut self,
runtime: &mut AnimationRuntime<C>,
from: K::Inner,
value: K::Inner,
) -> PropertyTransitionRegistration {
let replaced = self.active.take();
let replaced_handle = replaced.as_ref().map(ActivePropertyTransition::handle);
self.current = Some(value);
let registration = runtime.register_property_transition(
self.target,
self.property,
self.timing,
from,
value,
);
self.active = Some(ActivePropertyTransition::new(
registration.handle(),
from,
value,
runtime.clock().now(),
self.timing.total_duration(),
));
self.cleanup_replaced(runtime, replaced);
PropertyTransitionRegistration::new(registration, replaced_handle)
}
fn cleanup_replaced<C: AnimationClock>(
&self,
runtime: &mut AnimationRuntime<C>,
replaced: Option<ActivePropertyTransition<K>>,
) {
if let Some(active) = replaced {
runtime.cancel(self.target, active.handle());
}
}
fn current_visual_value<C: AnimationClock>(
&self,
runtime: &AnimationRuntime<C>,
) -> Option<K::Inner> {
let active = self.active.as_ref()?;
let snapshot = runtime.last_properties(self.target, active.handle())?;
let entry = snapshot.find_property(&self.property.raw())?;
K::unwrap_transition_value(entry.value())
}
fn invalidate_if_stale<C: AnimationClock>(&mut self, runtime: &AnimationRuntime<C>) {
if let Some(active) = &self.active
&& !runtime.contains(self.target, active.handle())
{
self.active = None;
}
}
}