summaryrefslogtreecommitdiff
path: root/rust/crates/animation/src/channel_layout.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
commitf5a82152394fde4d8f740f664ab7737b82861618 (patch)
tree5a51b181abead1ce03d2dbfc12b77918d3be0e1b /rust/crates/animation/src/channel_layout.rs
parente837e7504379e100ff3b56282577ec67bb88bec9 (diff)
downloadopencut-f5a82152394fde4d8f740f664ab7737b82861618.tar.gz
feat: GPUI desktop port foundation
- scene crate: full project/scene/element model, serde round-trip proven against the web SerializedProject shape - transfer crate: .ocp zip container, cross-compat with web fflate archives proven in both directions - storage crate: filesystem project store replacing IndexedDB, .ocp import/export - media crate: ffmpeg-next 8 decoder (frames, audio, thumbnails, waveform summaries) - playback crate: wall-clock anchored, frame-rounded playback clock - gpu crate: native texture readback; compositor renders headless offscreen on Vulkan (spike test) - flake devshell: rust toolchain, GPUI native deps, ffmpeg dev, bindgen clang args; GPUI hello-world builds - port plan in notes/gpui-port.md
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()
+ );
+ }
+}