use crate::reactive::{IntoSignal, IntoVal, Prop};
use super::{Animatable, Keyframes, TransitionConfig};
pub struct Animated<T> {
prop: Prop<T>,
motion: Option<Box<Motion<T>>>,
}
impl<T> Animated<T> {
pub fn entering_from(mut self, from: impl IntoVal<T>) -> Self {
match self.motion.as_deref_mut() {
Some(Motion::Ease { enter_from, .. }) => *enter_from = Some(from.into_val()),
Some(Motion::Play { .. }) => panic!(
"`entering_from` needs a transition to travel with, and a timeline \
already says where it starts — declare the enter on a \
`transition(..)` value instead"
),
None => unreachable!(
"an `Animated<T>` with no motion is only made by `into_animated`, \
inside the setter that immediately consumes it"
),
}
self
}
pub(crate) fn into_parts(self) -> (Prop<T>, Option<Box<Motion<T>>>) {
(self.prop, self.motion)
}
pub(crate) fn into_eased(self) -> (Prop<T>, Option<Eased<T>>) {
let ease = match self.motion.map(|motion| *motion) {
Some(Motion::Ease {
config,
enter_from,
exit_to,
}) => Some((config, enter_from, exit_to)),
None => None,
Some(Motion::Play { .. }) => {
unreachable!("a timeline needs T: Animatable, which this T is not")
}
};
(self.prop, ease)
}
}
impl<T: Clone + 'static> Animated<T> {
pub fn exiting_to<M>(mut self, to: impl IntoSignal<T, M>) -> Self {
let to = to.into_prop();
match self.motion.as_deref_mut() {
Some(Motion::Ease { exit_to, .. }) => {
*exit_to = Some(Box::new(move || {
to.get_untracked()
.expect("a declared exit always carries a value")
}))
}
Some(Motion::Play { .. }) => panic!(
"`exiting_to` needs a transition to travel with, and a timeline \
plays a sequence of its own — declare the exit on a \
`transition(..)` value instead"
),
None => unreachable!(
"an `Animated<T>` with no motion is only made by `into_animated`, \
inside the setter that immediately consumes it"
),
}
self
}
}
pub(crate) type Eased<T> = (TransitionConfig, Option<T>, Option<ExitTo<T>>);
pub(crate) type ExitTo<T> = Box<dyn Fn() -> T>;
pub(crate) enum Motion<T> {
Ease {
config: TransitionConfig,
enter_from: Option<T>,
exit_to: Option<ExitTo<T>>,
},
Play { keyframes: Keyframes<T> },
}
pub trait Animate<T: Clone + 'static, M>: IntoSignal<T, M> + Sized {
fn transition(self, transition: impl Into<TransitionConfig>) -> Animated<T> {
Animated {
prop: self.into_prop(),
motion: Some(Box::new(Motion::Ease {
config: transition.into(),
enter_from: None,
exit_to: None,
})),
}
}
fn timeline(self, keyframes: Keyframes<T>) -> Animated<T>
where
T: Animatable,
{
Animated {
prop: self.into_prop(),
motion: Some(Box::new(Motion::Play { keyframes })),
}
}
}
impl<T: Clone + 'static, M, S: IntoSignal<T, M>> Animate<T, M> for S {}
#[doc(hidden)]
pub struct AnimatedMarker;
#[doc(hidden)]
pub struct Plain<M>(std::marker::PhantomData<M>);
pub trait IntoAnimated<T: Clone + 'static, M> {
fn into_animated(self) -> Animated<T>;
}
impl<T: Clone + 'static, M, S: IntoSignal<T, M>> IntoAnimated<T, Plain<M>> for S {
fn into_animated(self) -> Animated<T> {
Animated {
prop: self.into_prop(),
motion: None,
}
}
}
impl<T: Clone + 'static> IntoAnimated<T, AnimatedMarker> for Animated<T> {
fn into_animated(self) -> Animated<T> {
self
}
}