summaryrefslogtreecommitdiff
path: root/rust/crates/animation/src/channel_layout.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/crates/animation/src/channel_layout.rs')
-rw-r--r--rust/crates/animation/src/channel_layout.rs124
1 files changed, 124 insertions, 0 deletions
diff --git a/rust/crates/animation/src/channel_layout.rs b/rust/crates/animation/src/channel_layout.rs
new file mode 100644
index 00000000..59bbc3f6
--- /dev/null
+++ b/rust/crates/animation/src/channel_layout.rs
@@ -0,0 +1,124 @@
+use scene::ParamValue;
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+use crate::color::{LinearRgba, parse_color_to_linear_rgba};
+use crate::types::AnimationInterpolation;
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub enum ChannelValueKind {
+ Scalar,
+ Discrete,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct ChannelComponentDefinition {
+ pub key: String,
+ pub value_kind: ChannelValueKind,
+ pub default_interpolation: AnimationInterpolation,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum ChannelLayout {
+ Leaf(ChannelComponentDefinition),
+ Composite(Vec<ChannelComponentDefinition>),
+}
+
+impl ChannelLayout {
+ pub fn components(&self) -> &[ChannelComponentDefinition] {
+ match self {
+ ChannelLayout::Leaf(component) => std::slice::from_ref(component),
+ ChannelLayout::Composite(components) => components,
+ }
+ }
+
+ pub fn decompose(&self, value: &ParamValue) -> Option<BTreeMap<String, ParamValue>> {
+ match self {
+ ChannelLayout::Leaf(component) => {
+ let mut values = BTreeMap::new();
+ values.insert(component.key.clone(), value.clone());
+ Some(values)
+ }
+ ChannelLayout::Composite(_) => {
+ let ParamValue::String(color) = value else {
+ return None;
+ };
+ let LinearRgba { r, g, b, a } = parse_color_to_linear_rgba(color)?;
+ let mut values = BTreeMap::new();
+ values.insert("r".to_string(), ParamValue::Number(r));
+ values.insert("g".to_string(), ParamValue::Number(g));
+ values.insert("b".to_string(), ParamValue::Number(b));
+ values.insert("a".to_string(), ParamValue::Number(a));
+ Some(values)
+ }
+ }
+ }
+}
+
+pub fn number_channel_layout() -> ChannelLayout {
+ ChannelLayout::Leaf(ChannelComponentDefinition {
+ key: "value".to_string(),
+ value_kind: ChannelValueKind::Scalar,
+ default_interpolation: AnimationInterpolation::Linear,
+ })
+}
+
+pub fn boolean_channel_layout() -> ChannelLayout {
+ ChannelLayout::Leaf(ChannelComponentDefinition {
+ key: "value".to_string(),
+ value_kind: ChannelValueKind::Discrete,
+ default_interpolation: AnimationInterpolation::Hold,
+ })
+}
+
+pub fn string_channel_layout() -> ChannelLayout {
+ ChannelLayout::Leaf(ChannelComponentDefinition {
+ key: "value".to_string(),
+ value_kind: ChannelValueKind::Discrete,
+ default_interpolation: AnimationInterpolation::Hold,
+ })
+}
+
+pub fn color_channel_layout() -> ChannelLayout {
+ let component = |key: &str| ChannelComponentDefinition {
+ key: key.to_string(),
+ value_kind: ChannelValueKind::Scalar,
+ default_interpolation: AnimationInterpolation::Linear,
+ };
+ ChannelLayout::Composite(vec![
+ component("r"),
+ component("g"),
+ component("b"),
+ component("a"),
+ ])
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn leaf_decompose_wraps_value_under_component_key() {
+ let layout = number_channel_layout();
+ let values = layout.decompose(&ParamValue::Number(3.0)).unwrap();
+ assert_eq!(values.get("value"), Some(&ParamValue::Number(3.0)));
+ }
+
+ #[test]
+ fn color_decompose_parses_hex_into_linear_components() {
+ let layout = color_channel_layout();
+ let values = layout
+ .decompose(&ParamValue::String("#ff0000".to_string()))
+ .unwrap();
+ assert_eq!(values.get("r"), Some(&ParamValue::Number(1.0)));
+ assert_eq!(values.get("g"), Some(&ParamValue::Number(0.0)));
+ assert_eq!(values.get("a"), Some(&ParamValue::Number(1.0)));
+ assert!(
+ layout
+ .decompose(&ParamValue::Number(1.0))
+ .is_none()
+ );
+ }
+}