use serde::{Deserialize, Serialize};
use crate::Meta;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
#[serde(tag = "type")]
pub enum Command {
Hello {
protocol_version: u32,
ui_version: String,
},
Seek {
frame: u32,
},
Play,
Pause,
Step {
delta: i32,
},
SetViewport {
w: u32,
h: u32,
dpr: f64,
},
SetMarker {
name: String,
time: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
scene: Option<String>,
},
SetEase {
name: String,
value: [f64; 4],
},
SetTunable {
name: String,
value: f64,
},
SetTransition {
scene: String,
seconds: f64,
},
Pick {
x: f64,
y: f64,
},
Select {
node_id: Option<u32>,
},
Shutdown,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
#[serde(tag = "type")]
pub enum Event {
Ready {
protocol_version: u32,
engine_version: String,
fps: u32,
size: [u32; 2],
scenes: Vec<SceneInfo>,
},
Reloaded,
Building,
Error {
kind: ErrorKind,
message: String,
#[serde(default)]
spans: Vec<Span>,
},
Frame {
index: u32,
},
Timeline {
frames: u32,
markers: Vec<MarkerInfo>,
scenes: Vec<SceneInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
audio_waveform: Option<Vec<f32>>,
},
Tree {
nodes: Vec<NodeInfo>,
},
Signals {
node_id: u32,
values: Vec<SignalValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
bounds: Option<[f64; 8]>,
},
Selected {
node_id: Option<u32>,
},
Playback {
playing: bool,
frame: u32,
},
MetaChanged {
scene: String,
meta: Meta,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub enum ErrorKind {
Incompatible,
Build,
Runtime,
Meta,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct Span {
pub file: String,
pub line: u32,
pub column: u32,
#[serde(default)]
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct SceneInfo {
pub name: String,
pub start: u32,
pub frames: u32,
#[serde(default)]
pub transition: u32,
#[serde(default)]
pub transition_at: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct MarkerInfo {
pub scene: String,
pub name: String,
pub time: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct NodeInfo {
pub id: u32,
pub parent: Option<u32>,
pub kind: String,
pub name: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "ts", derive(ts_rs::TS), ts(export))]
pub struct SignalValue {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FrameFormat {
Webp = 1,
Jpeg = 2,
Rgba = 3,
Surface = 4,
}
impl FrameFormat {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
1 => Some(Self::Webp),
2 => Some(Self::Jpeg),
3 => Some(Self::Rgba),
4 => Some(Self::Surface),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameHeader {
pub frame: u32,
pub width: u16,
pub height: u16,
pub format: FrameFormat,
pub len: u32,
}
impl FrameHeader {
pub const SIZE: usize = 16;
pub fn encode(&self) -> [u8; Self::SIZE] {
let mut out = [0u8; Self::SIZE];
out[0..4].copy_from_slice(&self.frame.to_le_bytes());
out[4..6].copy_from_slice(&self.width.to_le_bytes());
out[6..8].copy_from_slice(&self.height.to_le_bytes());
out[8] = self.format as u8;
out[12..16].copy_from_slice(&self.len.to_le_bytes());
out
}
pub fn decode(bytes: &[u8]) -> Option<Self> {
let b: &[u8; Self::SIZE] = bytes.get(..Self::SIZE)?.try_into().ok()?;
Some(Self {
frame: u32::from_le_bytes([b[0], b[1], b[2], b[3]]),
width: u16::from_le_bytes([b[4], b[5]]),
height: u16::from_le_bytes([b[6], b[7]]),
format: FrameFormat::from_u8(b[8])?,
len: u32::from_le_bytes([b[12], b[13], b[14], b[15]]),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn command_json_shape() {
let json = serde_json::to_string(&Command::Seek { frame: 42 }).unwrap();
assert_eq!(json, r#"{"type":"Seek","frame":42}"#);
let back: Command = serde_json::from_str(r#"{"type":"Play"}"#).unwrap();
assert_eq!(back, Command::Play);
}
#[test]
fn frame_header_roundtrip() {
let h = FrameHeader {
frame: 1234,
width: 960,
height: 540,
format: FrameFormat::Webp,
len: 65_000,
};
assert_eq!(FrameHeader::decode(&h.encode()), Some(h));
}
}