use crate::animation_spec_schema::{
AnimationOverrideJson, AnimationSpec as AnimationSpecJson, Animations as AnimationsJson,
BezierCurve as BezierCurveJson, CustomKeyframe as CustomKeyframeJson,
CustomTimeline as CustomTimelineJson, Duration, Easing as EasingJson, KeyFrame as KeyFrameJson,
KeyFrameAnimation as KeyFrameAnimationJson, RepeatType as RepeatTypeJson,
SmoothAnimation as SmoothAnimationJson, StopType as StopTypeJson,
};
use dc_bundle::animationspec;
use dc_bundle::animationspec::{
animation_override, animations, easing, repeat_type, stop_type, AnimationSpec, Animations,
BezierCurve, CustomKeyframe, CustomTimeline, Easing, KeyFrame, KeyFrameAnimation, RepeatType,
SmoothAnimation, StopType,
};
pub struct AnimationOverride(animationspec::AnimationOverride);
impl From<AnimationOverride> for animationspec::AnimationOverride {
fn from(val: AnimationOverride) -> Self {
val.0
}
}
impl From<&AnimationOverrideJson> for AnimationOverride {
fn from(json: &AnimationOverrideJson) -> Self {
AnimationOverride(match json {
AnimationOverrideJson::Default => animationspec::AnimationOverride {
animation_override: Some(animation_override::Animation_override::NoOverride(
::protobuf::well_known_types::empty::Empty::new(),
)),
..Default::default()
},
AnimationOverrideJson::DisableAnimations => animationspec::AnimationOverride {
animation_override: Some(
animation_override::Animation_override::DisableAnimations(
::protobuf::well_known_types::empty::Empty::new(),
),
),
..Default::default()
},
AnimationOverrideJson::Custom(spec) => animationspec::AnimationOverride {
animation_override: Some(animation_override::Animation_override::Custom(
spec.clone().into(),
)),
..Default::default()
},
})
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum CustomKeyframeValueJson {
Scalar(f64),
String(String),
Color(crate::figma_schema::FigmaColor),
}
impl From<CustomKeyframeJson> for CustomKeyframe {
fn from(json: CustomKeyframeJson) -> Self {
let mut typed_value = animationspec::CustomKeyframeValue::default();
if let Ok(val) = serde_json::from_value::<CustomKeyframeValueJson>(json.value_json.clone())
{
match val {
CustomKeyframeValueJson::Scalar(scalar) => {
typed_value.value =
Some(animationspec::custom_keyframe_value::Value::Scalar(scalar as f32));
}
CustomKeyframeValueJson::String(s) => {
if s.starts_with('#') {
let hex = &s[1..];
if hex.len() == 6 {
if let (Ok(r), Ok(g), Ok(b)) = (
u32::from_str_radix(&hex[0..2], 16),
u32::from_str_radix(&hex[2..4], 16),
u32::from_str_radix(&hex[4..6], 16),
) {
typed_value.value =
Some(animationspec::custom_keyframe_value::Value::Color(
animationspec::RgbaValue {
r,
g,
b,
a: 255,
..Default::default()
},
));
}
} else if hex.len() == 8 {
if let (Ok(r), Ok(g), Ok(b), Ok(a)) = (
u32::from_str_radix(&hex[0..2], 16),
u32::from_str_radix(&hex[2..4], 16),
u32::from_str_radix(&hex[4..6], 16),
u32::from_str_radix(&hex[6..8], 16),
) {
typed_value.value =
Some(animationspec::custom_keyframe_value::Value::Color(
animationspec::RgbaValue {
r,
g,
b,
a,
..Default::default()
},
));
}
}
}
}
CustomKeyframeValueJson::Color(color) => {
typed_value.value = Some(animationspec::custom_keyframe_value::Value::Color(
animationspec::RgbaValue {
r: (color.r * 255.0) as u32,
g: (color.g * 255.0) as u32,
b: (color.b * 255.0) as u32,
a: (color.a * 255.0) as u32,
..Default::default()
},
));
}
}
}
if typed_value.value.is_none() {
log::warn!("Failed to parse custom keyframe value JSON: {:?}", json.value_json);
}
CustomKeyframe {
fraction: json.fraction,
value: ::protobuf::MessageField::some(typed_value),
easing: Some(json.easing.into()).into(),
..Default::default()
}
}
}
impl From<CustomTimelineJson> for CustomTimeline {
fn from(json: CustomTimelineJson) -> Self {
CustomTimeline {
target_easing: Some(json.target_easing.into()).into(),
keyframes: json.keyframes.into_iter().map(|k| k.into()).collect(),
..Default::default()
}
}
}
impl From<AnimationSpecJson> for AnimationSpec {
fn from(json: AnimationSpecJson) -> Self {
AnimationSpec {
initial_delay: Some(json.initial_delay.into()).into(),
animation: Some(json.animation.into()).into(),
interrupt_type: json.interrupt_type.map(|x| x.into()).into(),
custom_keyframe_data: json
.custom_keyframe_data
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
..Default::default()
}
}
}
impl From<AnimationsJson> for Animations {
fn from(json: AnimationsJson) -> Self {
let mut anim = Animations::new();
if let Some(smooth) = json.smooth {
anim.type_ = Some(animations::Type::Smooth(smooth.into()));
} else if let Some(key_frame) = json.key_frame {
anim.type_ = Some(animations::Type::KeyFrame(key_frame.into()));
}
anim
}
}
impl From<SmoothAnimationJson> for SmoothAnimation {
fn from(json: SmoothAnimationJson) -> Self {
SmoothAnimation {
duration: Some(json.duration.into()).into(),
repeat_type: Some(json.repeat_type.into()).into(),
easing: Some(json.easing.into()).into(),
..Default::default()
}
}
}
impl From<KeyFrameAnimationJson> for KeyFrameAnimation {
fn from(json: KeyFrameAnimationJson) -> Self {
KeyFrameAnimation {
steps: json.steps.into_iter().map(|x| x.into()).collect(),
repeat_type: Some(json.repeat_type.into()).into(),
..Default::default()
}
}
}
impl From<KeyFrameJson> for KeyFrame {
fn from(json: KeyFrameJson) -> Self {
KeyFrame {
value: json.value,
duration: Some(json.duration.into()).into(),
..Default::default()
}
}
}
impl From<RepeatTypeJson> for RepeatType {
fn from(json: RepeatTypeJson) -> Self {
let mut repeat_type = RepeatType::new();
match json {
RepeatTypeJson::String(s) => {
if s == "LoopForever" {
repeat_type.type_ = Some(repeat_type::Type::LoopForever(
::protobuf::well_known_types::empty::Empty::new(),
));
} else {
repeat_type.type_ = Some(repeat_type::Type::NoRepeat(
::protobuf::well_known_types::empty::Empty::new(),
));
}
}
RepeatTypeJson::Repeat { repeat: r } => {
repeat_type.type_ = Some(repeat_type::Type::Repeat(r));
}
}
repeat_type
}
}
impl From<EasingJson> for Easing {
fn from(json: EasingJson) -> Self {
let mut easing = Easing::new();
match json {
EasingJson::String(s) => {
let s_norm = s.to_lowercase().replace("_", "").replace("-", "");
if s_norm == "linear" {
easing.type_ = Some(easing::Type::Linear(
::protobuf::well_known_types::empty::Empty::new(),
));
} else if s_norm == "easein" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.42;
b.p1 = 0.0;
b.p2 = 1.0;
b.p3 = 1.0;
}
} else if s_norm == "easeout" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.0;
b.p1 = 0.0;
b.p2 = 0.58;
b.p3 = 1.0;
}
} else if s_norm == "easeinout" || s_norm == "easeinandout" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.42;
b.p1 = 0.0;
b.p2 = 0.58;
b.p3 = 1.0;
}
} else if s_norm == "easeincubic" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.32;
b.p1 = 0.0;
b.p2 = 0.67;
b.p3 = 0.0;
}
} else if s_norm == "easeoutcubic" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.33;
b.p1 = 1.0;
b.p2 = 0.68;
b.p3 = 1.0;
}
} else if s_norm == "easeinoutcubic" {
easing.type_ = Some(easing::Type::Bezier(Default::default()));
if let Some(easing::Type::Bezier(ref mut b)) = easing.type_ {
b.p0 = 0.645;
b.p1 = 0.045;
b.p2 = 0.355;
b.p3 = 1.0;
}
} else {
easing.type_ = Some(easing::Type::Linear(
::protobuf::well_known_types::empty::Empty::new(),
));
}
}
EasingJson::Bezier { bezier: b } => {
easing.type_ = Some(easing::Type::Bezier(b.into()));
}
}
easing
}
}
impl From<BezierCurveJson> for BezierCurve {
fn from(json: BezierCurveJson) -> Self {
BezierCurve { p0: json.p0, p1: json.p1, p2: json.p2, p3: json.p3, ..Default::default() }
}
}
impl From<StopTypeJson> for StopType {
fn from(json: StopTypeJson) -> Self {
let mut stop_type = StopType::new();
match json {
StopTypeJson::None => {}
StopTypeJson::ResetToStart => {
stop_type.type_ = Some(stop_type::Type::ResetToStart(
::protobuf::well_known_types::empty::Empty::new(),
));
}
StopTypeJson::Complete => {
stop_type.type_ = Some(stop_type::Type::Complete(
::protobuf::well_known_types::empty::Empty::new(),
));
}
StopTypeJson::Stop => {
stop_type.type_ =
Some(stop_type::Type::Stop(::protobuf::well_known_types::empty::Empty::new()));
}
}
stop_type
}
}
impl From<Duration> for ::protobuf::well_known_types::duration::Duration {
fn from(json: Duration) -> Self {
::protobuf::well_known_types::duration::Duration {
seconds: json.secs,
nanos: json.nanos as i32,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::animation_spec_schema::{
AnimationOverrideJson, AnimationSpec as AnimationSpecJson, Animations as AnimationsJson,
BezierCurve as BezierCurveJson, Duration, Easing as EasingJson, KeyFrame as KeyFrameJson,
KeyFrameAnimation as KeyFrameAnimationJson, RepeatType as RepeatTypeJson,
SmoothAnimation as SmoothAnimationJson, StopType as StopTypeJson,
};
use dc_bundle::animationspec::{
animation_override, animations, easing, repeat_type, stop_type,
};
#[test]
fn test_animation_override_from_json() {
let json_default = AnimationOverrideJson::Default;
let proto_override: AnimationOverride = (&json_default).into();
assert!(matches!(
proto_override.0,
animationspec::AnimationOverride {
animation_override: Some(animation_override::Animation_override::NoOverride(_)),
..
}
));
let json_disable = AnimationOverrideJson::DisableAnimations;
let proto_override: AnimationOverride = (&json_disable).into();
assert!(matches!(
proto_override.0,
animationspec::AnimationOverride {
animation_override: Some(
animation_override::Animation_override::DisableAnimations(_)
),
..
}
));
let json_custom = AnimationOverrideJson::Custom(AnimationSpecJson::default());
let proto_override: AnimationOverride = (&json_custom).into();
assert!(matches!(
proto_override.0,
animationspec::AnimationOverride {
animation_override: Some(animation_override::Animation_override::Custom(_)),
..
}
));
}
#[test]
fn test_animation_spec_from_json() {
let json_spec = AnimationSpecJson {
initial_delay: Duration { secs: 1, nanos: 500_000_000.0 },
animation: AnimationsJson::default(),
interrupt_type: Some(StopTypeJson::Complete),
custom_keyframe_data: std::collections::HashMap::new(),
};
let proto_spec: AnimationSpec = json_spec.into();
assert_eq!(proto_spec.initial_delay.get_or_default().seconds, 1);
assert_eq!(proto_spec.initial_delay.get_or_default().nanos, 500_000_000);
assert!(proto_spec.animation.is_some());
assert!(matches!(
proto_spec.interrupt_type.as_ref().unwrap().type_,
Some(stop_type::Type::Complete(_))
));
let json_spec_no_interrupt = AnimationSpecJson {
initial_delay: Duration { secs: 1, nanos: 500_000_000.0 },
animation: AnimationsJson::default(),
interrupt_type: None,
custom_keyframe_data: std::collections::HashMap::new(),
};
let proto_spec_no_interrupt: AnimationSpec = json_spec_no_interrupt.into();
assert!(proto_spec_no_interrupt.interrupt_type.is_none());
}
#[test]
fn test_animations_from_json() {
let json_smooth =
AnimationsJson { smooth: Some(SmoothAnimationJson::default()), key_frame: None };
let proto_anim: Animations = json_smooth.into();
assert!(matches!(proto_anim.type_, Some(animations::Type::Smooth(_))));
let json_keyframe =
AnimationsJson { smooth: None, key_frame: Some(KeyFrameAnimationJson::default()) };
let proto_anim: Animations = json_keyframe.into();
assert!(matches!(proto_anim.type_, Some(animations::Type::KeyFrame(_))));
let json_none = AnimationsJson { smooth: None, key_frame: None };
let proto_anim: Animations = json_none.into();
assert!(proto_anim.type_.is_none());
}
#[test]
fn test_smooth_animation_from_json() {
let json_smooth = SmoothAnimationJson {
duration: Duration { secs: 2, nanos: 0.0 },
repeat_type: RepeatTypeJson::String("LoopForever".to_string()),
easing: EasingJson::String("Linear".to_string()),
};
let proto_smooth: SmoothAnimation = json_smooth.into();
assert_eq!(proto_smooth.duration.get_or_default().seconds, 2);
assert_eq!(proto_smooth.duration.get_or_default().nanos, 0);
assert!(matches!(
proto_smooth.repeat_type.get_or_default().type_,
Some(repeat_type::Type::LoopForever(_))
));
assert!(matches!(
proto_smooth.easing.get_or_default().type_,
Some(easing::Type::Linear(_))
));
}
#[test]
fn test_key_frame_animation_from_json() {
let json_keyframe = KeyFrameAnimationJson {
steps: vec![KeyFrameJson::default()],
repeat_type: RepeatTypeJson::Repeat { repeat: 3 },
};
let proto_keyframe: KeyFrameAnimation = json_keyframe.into();
assert_eq!(proto_keyframe.steps.len(), 1);
assert!(matches!(
proto_keyframe.repeat_type.get_or_default().type_,
Some(repeat_type::Type::Repeat(3))
));
}
#[test]
fn test_key_frame_from_json() {
let json_keyframe =
KeyFrameJson { value: 0.5, duration: Duration { secs: 0, nanos: 100_000_000.0 } };
let proto_keyframe: KeyFrame = json_keyframe.into();
assert_eq!(proto_keyframe.value, 0.5);
assert_eq!(proto_keyframe.duration.get_or_default().seconds, 0);
assert_eq!(proto_keyframe.duration.get_or_default().nanos, 100_000_000);
}
#[test]
fn test_repeat_type_from_json() {
let json_loop = RepeatTypeJson::String("LoopForever".to_string());
let proto_repeat: RepeatType = json_loop.into();
assert!(matches!(proto_repeat.type_, Some(repeat_type::Type::LoopForever(_))));
let json_no_repeat = RepeatTypeJson::String("NoRepeat".to_string());
let proto_repeat: RepeatType = json_no_repeat.into();
assert!(matches!(proto_repeat.type_, Some(repeat_type::Type::NoRepeat(_))));
let json_repeat = RepeatTypeJson::Repeat { repeat: 5 };
let proto_repeat: RepeatType = json_repeat.into();
assert!(matches!(proto_repeat.type_, Some(repeat_type::Type::Repeat(5))));
}
#[test]
fn test_easing_from_json() {
let json_linear = EasingJson::String("Linear".to_string());
let proto_easing: Easing = json_linear.into();
assert!(matches!(proto_easing.type_, Some(easing::Type::Linear(_))));
let json_bezier =
EasingJson::Bezier { bezier: BezierCurveJson { p0: 0.1, p1: 0.2, p2: 0.3, p3: 0.4 } };
let proto_easing: Easing = json_bezier.into();
match proto_easing.type_ {
Some(easing::Type::Bezier(b)) => {
assert_eq!(b.p0, 0.1);
assert_eq!(b.p1, 0.2);
assert_eq!(b.p2, 0.3);
assert_eq!(b.p3, 0.4);
}
_ => panic!("Expected Bezier easing type, but got {:?}", proto_easing.type_),
}
}
#[test]
fn test_bezier_curve_from_json() {
let json_bezier = BezierCurveJson { p0: 0.1, p1: 0.2, p2: 0.3, p3: 0.4 };
let proto_bezier: BezierCurve = json_bezier.into();
assert_eq!(proto_bezier.p0, 0.1);
assert_eq!(proto_bezier.p1, 0.2);
assert_eq!(proto_bezier.p2, 0.3);
assert_eq!(proto_bezier.p3, 0.4);
}
#[test]
fn test_stop_type_from_json() {
let json_none = StopTypeJson::None;
let proto_stop: StopType = json_none.into();
assert!(proto_stop.type_.is_none());
let json_reset = StopTypeJson::ResetToStart;
let proto_stop: StopType = json_reset.into();
assert!(matches!(proto_stop.type_, Some(stop_type::Type::ResetToStart(_))));
let json_complete = StopTypeJson::Complete;
let proto_stop: StopType = json_complete.into();
assert!(matches!(proto_stop.type_, Some(stop_type::Type::Complete(_))));
let json_stop = StopTypeJson::Stop;
let proto_stop: StopType = json_stop.into();
assert!(matches!(proto_stop.type_, Some(stop_type::Type::Stop(_))));
}
#[test]
fn test_duration_from_json() {
let json_duration = Duration { secs: 10, nanos: 123.0 };
let proto_duration: ::protobuf::well_known_types::duration::Duration = json_duration.into();
assert_eq!(proto_duration.seconds, 10);
assert_eq!(proto_duration.nanos, 123);
}
}