use std::collections::BTreeMap;
use serde::Deserialize;
use serde::de::{self, Deserializer, MapAccess, Visitor};
pub type SharedId = u32;
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Driver {
Timing {
to: f32,
#[serde(default = "default_duration")]
duration: f32,
#[serde(default)]
easing: Easing,
},
Spring {
to: f32,
#[serde(default = "default_stiffness")]
stiffness: f32,
#[serde(default = "default_damping")]
damping: f32,
#[serde(default = "default_mass")]
mass: f32,
},
Repeat {
animation: Box<Driver>,
#[serde(default = "default_count")]
count: i32,
#[serde(default)]
reverse: bool,
},
Sequence { steps: Vec<Driver> },
Delay { delay: f32, animation: Box<Driver> },
}
#[derive(Debug, Clone, Copy, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Easing {
#[default]
Linear,
EaseIn,
EaseOut,
EaseInOut,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "kind", rename_all = "camelCase")]
pub enum AnimationCommand {
Declare { id: SharedId, initial: f32 },
Set { id: SharedId, value: f32 },
Animate {
id: SharedId,
driver: Driver,
#[serde(default)]
token: Option<u64>,
},
Cancel { id: SharedId },
Clear,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Binding {
Shared { id: SharedId },
Interpolate {
id: SharedId,
input: Vec<f32>,
output: Vec<f32>,
},
InterpolateColor {
id: SharedId,
input: Vec<f32>,
output: Vec<[f32; 4]>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AnimatableProperty {
TranslateX,
TranslateY,
Scale,
ScaleX,
ScaleY,
Rotate,
Opacity,
BackgroundColor,
BorderColor,
Color,
Width,
Height,
MinWidth,
MinHeight,
MaxWidth,
MaxHeight,
Left,
Right,
Top,
Bottom,
FlexBasis,
Gap,
RowGap,
ColumnGap,
AspectRatio,
}
impl AnimatableProperty {
pub fn from_wire(key: &str) -> Option<Self> {
Some(match key {
"translateX" => Self::TranslateX,
"translateY" => Self::TranslateY,
"scale" => Self::Scale,
"scaleX" => Self::ScaleX,
"scaleY" => Self::ScaleY,
"rotate" => Self::Rotate,
"opacity" => Self::Opacity,
"backgroundColor" => Self::BackgroundColor,
"borderColor" => Self::BorderColor,
"color" => Self::Color,
"width" => Self::Width,
"height" => Self::Height,
"minWidth" => Self::MinWidth,
"minHeight" => Self::MinHeight,
"maxWidth" => Self::MaxWidth,
"maxHeight" => Self::MaxHeight,
"left" => Self::Left,
"right" => Self::Right,
"top" => Self::Top,
"bottom" => Self::Bottom,
"flexBasis" => Self::FlexBasis,
"gap" => Self::Gap,
"rowGap" => Self::RowGap,
"columnGap" => Self::ColumnGap,
"aspectRatio" => Self::AspectRatio,
_ => return None,
})
}
pub fn value_kind(self) -> ValueKind {
match self {
Self::TranslateX
| Self::TranslateY
| Self::Width
| Self::Height
| Self::MinWidth
| Self::MinHeight
| Self::MaxWidth
| Self::MaxHeight
| Self::Left
| Self::Right
| Self::Top
| Self::Bottom
| Self::FlexBasis
| Self::Gap
| Self::RowGap
| Self::ColumnGap => ValueKind::Length,
Self::Scale | Self::ScaleX | Self::ScaleY | Self::Opacity | Self::AspectRatio => {
ValueKind::Scalar
}
Self::Rotate => ValueKind::Angle,
Self::BackgroundColor | Self::BorderColor | Self::Color => ValueKind::Color,
}
}
pub fn is_transform(self) -> bool {
matches!(
self,
Self::TranslateX
| Self::TranslateY
| Self::Scale
| Self::ScaleX
| Self::ScaleY
| Self::Rotate
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueKind {
Scalar,
Length,
Color,
Angle,
}
#[derive(Debug, Clone, Default)]
pub struct AnimatedBindings(pub BTreeMap<AnimatableProperty, Binding>);
impl AnimatedBindings {
pub fn get(&self, property: AnimatableProperty) -> Option<&Binding> {
self.0.get(&property)
}
pub fn contains(&self, property: AnimatableProperty) -> bool {
self.0.contains_key(&property)
}
pub fn has_transform(&self) -> bool {
self.0.keys().any(|p| p.is_transform())
}
pub fn iter(&self) -> impl Iterator<Item = (&AnimatableProperty, &Binding)> {
self.0.iter()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl<'de> Deserialize<'de> for AnimatedBindings {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct BindingsVisitor;
impl<'de> Visitor<'de> for BindingsVisitor {
type Value = AnimatedBindings;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a map of animatable style properties to bindings")
}
fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut out = BTreeMap::new();
while let Some(key) = map.next_key::<String>()? {
match AnimatableProperty::from_wire(&key) {
Some(property) => {
out.insert(property, map.next_value::<Binding>()?);
}
None => {
map.next_value::<de::IgnoredAny>()?;
tracing::warn!(
target: "bevy_react",
"animatedStyle: ignoring unknown property {key:?}"
);
}
}
}
Ok(AnimatedBindings(out))
}
}
deserializer.deserialize_map(BindingsVisitor)
}
}
fn default_duration() -> f32 {
0.3
}
fn default_stiffness() -> f32 {
100.0
}
fn default_damping() -> f32 {
10.0
}
fn default_mass() -> f32 {
1.0
}
fn default_count() -> i32 {
1
}