tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ main
shared/foreign-lottie/src/schema/animated_properties/shape_property.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
1// Copyright 2024 the Velato Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::schema::helpers::bezier::Bezier;
5use crate::schema::helpers::int_boolean::BoolInt;
6use serde::de::{self, Deserializer};
7use serde::{Deserialize, Serialize};
8
9use super::shape_keyframe::ShapeKeyframe;
10
11/// An animatable property that holds a Bezier
12#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
13pub struct ShapeProperty {
14 /// The index of the property.
15 #[serde(rename = "ix")]
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub property_index: Option<f64>,
18 /// Whether the property is animated
19 #[serde(rename = "a")]
20 #[serde(skip_serializing_if = "Option::is_none")]
21 pub animated: Option<BoolInt>,
22 /// The expression for the property.
23 #[serde(rename = "x")]
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub expression: Option<String>,
26 /// See [`ShapePropertyK`]
27 #[serde(rename = "k")]
28 pub value: ShapePropertyK,
29}
30
31/// The possible values of "k" in a [`ShapeProperty`].
32/// Either a static bezier held, or an animation with keyframes defined.
33#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
34#[serde(untagged)]
35pub enum ShapePropertyK {
36 #[serde(deserialize_with = "deserialize_keyframes")]
37 Animated(Vec<ShapeKeyframe>),
38 Static(Bezier),
39}
40
41/// Custom deserializer ensures that every keyframe has a "s" start, except only the last keyframe which may omit this.
42pub fn deserialize_keyframes<'de, D>(deserializer: D) -> Result<Vec<ShapeKeyframe>, D::Error>
43where
44 D: Deserializer<'de>,
45{
46 // Deserialize the keyframes & handle empty case
47 let keyframes = Vec::<ShapeKeyframe>::deserialize(deserializer)?;
48 if keyframes.is_empty() {
49 return Ok(keyframes);
50 }
51
52 // Validate that all keyframes until the last have a start property
53 if !keyframes[0..(keyframes.len() - 1)]
54 .iter()
55 .all(|keyframe| keyframe.start.is_some())
56 {
57 return Err(de::Error::custom(
58 "Animated Shape Keyframe found with missing 's' start property. Only the last keyframe may omit this.",
59 ));
60 }
61
62 // Early return if last keyframe has a start value.
63 if let Some(last_keyframe) = keyframes.last()
64 && last_keyframe.start.is_some()
65 {
66 return Ok(keyframes);
67 }
68
69 // The last keyframe has no start value - so there must be at least one other keyframe present.
70 if keyframes.len() < 2 {
71 Err(de::Error::custom(
72 "Last Animated Shape Keyframe 's' was omitted where only one keyframe is present.",
73 ))
74 } else {
75 Ok(keyframes)
76 }
77}