use bevy::prelude::*;
use serde::Deserialize;
use crate::animations::{Driver, Easing};
use crate::protocol::{
animatable::AnimatableField, units::Length, units::Rect, units::Time as WireTime,
};
use crate::style::Style;
use crate::ui_map::parse_color;
use super::color_to_rgba;
use crate::style::props::{
BACKGROUND_COLOR, BORDER_RADIUS, HEIGHT, MAX_HEIGHT, MAX_WIDTH, MORPH_FILTER, OPACITY,
TRANSFORM, TRANSFORM3D, TRANSITION, WIDTH,
};
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transition {
pub transform: Option<ChannelTransition>,
pub opacity: Option<ChannelTransition>,
pub background_color: Option<ChannelTransition>,
pub size: Option<ChannelTransition>,
pub border_radius: Option<ChannelTransition>,
pub scroll: Option<ChannelTransition>,
pub filter: Option<ChannelTransition>,
pub backdrop_filter: Option<ChannelTransition>,
pub transform3d: Option<ChannelTransition>,
pub morph_filter: Option<ChannelTransition>,
pub background_gradient: Option<ChannelTransition>,
pub border_gradient: Option<ChannelTransition>,
pub layout: Option<ChannelTransition>,
pub shared_element: Option<ChannelTransition>,
}
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.transform.as_ref(),
C::Opacity => self.opacity.as_ref(),
C::Background => self.background_color.as_ref(),
C::BorderRadius => self.border_radius.as_ref(),
C::Filter => self.filter.as_ref(),
C::Backdrop => self.backdrop_filter.as_ref(),
C::Transform3d => self.transform3d.as_ref(),
C::BackgroundGradient => self.background_gradient.as_ref(),
C::BorderGradient => self.border_gradient.as_ref(),
}
}
}
#[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
}
pub(super) fn morph_default() -> &'static ChannelTransition {
static DEFAULT: std::sync::LazyLock<ChannelTransition> =
std::sync::LazyLock::new(|| ChannelTransition {
duration: None, easing: Easing::EaseInOut,
delay: WireTime::default(),
stiffness: None,
damping: None,
mass: 1.0,
});
&DEFAULT
}
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 border_radius: Option<Rect>,
pub transform3d: Option<crate::protocol::transform::Transform3d>,
}
impl TransitionInput {
pub(super) fn from_style(style: &Style) -> Option<Self> {
let spec = match style.get(&TRANSITION).cloned() {
Some(spec) => spec,
None if style.get(&MORPH_FILTER).is_some() => Transition::default(),
None => return None,
};
let t = style.get(&TRANSFORM).cloned().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.get(&OPACITY).static_val(),
background_color: style
.get(&BACKGROUND_COLOR)
.static_ref()
.map(|hex| color_to_rgba(parse_color(hex))),
width: style.get(&WIDTH).static_val(),
height: style.get(&HEIGHT).static_val(),
max_width: style.get(&MAX_WIDTH).static_val(),
max_height: style.get(&MAX_HEIGHT).static_val(),
border_radius: style.get(&BORDER_RADIUS).static_val(),
transform3d: style.get(&TRANSFORM3D).cloned(),
})
}
}