use bevy::prelude::*;
use serde::Deserialize;
use crate::animations::{Driver, Easing};
use crate::protocol::{
animatable::AnimatableField, style::Style, units::Length, units::Time as WireTime,
};
use crate::ui_map::parse_color;
use super::color_to_rgba;
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transition {
pub all: Option<ChannelTransition>,
pub transform: Option<ChannelTransition>,
pub opacity: Option<ChannelTransition>,
pub background_color: Option<ChannelTransition>,
pub size: Option<ChannelTransition>,
pub scroll: Option<ChannelTransition>,
pub filter: Option<ChannelTransition>,
pub backdrop_filter: Option<ChannelTransition>,
pub transform3d: Option<ChannelTransition>,
}
macro_rules! transition_channels {
($cb:ident) => {
$cb! {
(for_transform, transform, "the transform channels"),
(for_opacity, opacity, "opacity"),
(for_background, background_color, "background color"),
(for_size, size, "the size channels"),
(for_scroll, scroll, "the scroll offset"),
(for_filter, filter, "the filter chain"),
(for_backdrop_filter, backdrop_filter, "the backdrop-filter chain"),
(for_transform3d, transform3d, "the transform3d channels"),
}
};
}
macro_rules! spec_accessors {
($(($accessor:ident, $field:ident, $doc:literal),)*) => {
impl Transition {
$(
#[doc = concat!("The transition for ", $doc, " (explicit, else `all`).")]
pub fn $accessor(&self) -> Option<&ChannelTransition> {
self.$field.as_ref().or(self.all.as_ref())
}
)*
}
};
}
transition_channels!(spec_accessors);
impl Transition {
pub(crate) fn resolve(
&self,
channel: crate::animations::props::ChannelId,
) -> Option<&ChannelTransition> {
use crate::animations::props::ChannelId as C;
match channel {
C::Transform => self.for_transform(),
C::Opacity => self.for_opacity(),
C::Background => self.for_background(),
C::Filter => self.for_filter(),
C::Backdrop => self.for_backdrop_filter(),
C::Transform3d => self.for_transform3d(),
}
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelTransition {
pub duration: Option<WireTime>,
#[serde(default)]
pub easing: Easing,
#[serde(default)]
pub delay: WireTime,
pub stiffness: Option<f32>,
pub damping: Option<f32>,
#[serde(default = "default_mass")]
pub mass: f32,
}
fn default_mass() -> f32 {
1.0
}
impl ChannelTransition {
pub(super) fn to_driver(&self, to: f32) -> Driver {
if self.stiffness.is_some() || self.damping.is_some() {
Driver::Spring {
to,
stiffness: self.stiffness.unwrap_or(100.0),
damping: self.damping.unwrap_or(10.0),
mass: self.mass,
}
} else {
let timing = Driver::Timing {
to,
duration: self.duration.map(WireTime::seconds).unwrap_or(0.3),
easing: self.easing,
};
let delay = self.delay.seconds();
if delay > 0.0 {
Driver::Delay {
delay,
animation: Box::new(timing),
}
} else {
timing
}
}
}
}
#[derive(Component, Debug, Clone, Default)]
pub struct TransitionInput {
pub spec: Transition,
pub translate_x: Option<Length>,
pub translate_y: Option<Length>,
pub scale: Option<f32>,
pub scale_x: Option<f32>,
pub scale_y: Option<f32>,
pub rotate: Option<f32>,
pub opacity: Option<f32>,
pub background_color: Option<[f32; 4]>,
pub width: Option<Length>,
pub height: Option<Length>,
pub max_width: Option<Length>,
pub max_height: Option<Length>,
pub transform3d: Option<crate::protocol::transform::Transform3d>,
}
impl TransitionInput {
pub(super) fn from_style(style: &Style) -> Option<Self> {
let spec = style.transition.clone()?;
let t = style.transform.clone().unwrap_or_default();
Some(Self {
spec,
translate_x: t.translate_x.static_val(),
translate_y: t.translate_y.static_val(),
scale: t.scale.static_val(),
scale_x: t.scale_x.static_val(),
scale_y: t.scale_y.static_val(),
rotate: t
.rotate
.static_val()
.map(crate::protocol::units::Angle::radians),
opacity: style.opacity.static_val(),
background_color: style
.background_color
.static_ref()
.map(|hex| color_to_rgba(parse_color(hex))),
width: style.width.static_val(),
height: style.height.static_val(),
max_width: style.max_width.static_val(),
max_height: style.max_height.static_val(),
transform3d: style.transform3d.clone(),
})
}
}