use serde::{Deserialize, Serialize};
use super::registry_io::InstancePayload;
use crate::coord::CanvasRect;
pub const CONTAINER_VERSION: u32 = 1;
pub const FORMAT_TAG: &str = "darkly";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Manifest {
pub format: String,
pub container_version: u32,
pub writer: ManifestWriter,
pub name: String,
pub canvas: ManifestCanvas,
pub requires: ManifestRequires,
pub composite: String,
pub root: u64,
pub nodes: Vec<ManifestEntry>,
pub modifiers: Vec<ManifestEntry>,
#[serde(default)]
pub selection_id: Option<u64>,
pub veils: Vec<ManifestVeil>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ManifestEntry {
pub id: u64,
#[serde(rename = "type")]
pub type_id: String,
pub body: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ManifestWriter {
pub name: String,
pub version: String,
}
impl ManifestWriter {
pub fn current() -> Self {
ManifestWriter {
name: "darkly".to_string(),
version: crate::VERSION.to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestCanvas {
pub width: u32,
pub height: u32,
#[serde(default)]
pub origin_x: i32,
#[serde(default)]
pub origin_y: i32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestRequires {
#[serde(default)]
pub veil: Vec<String>,
#[serde(default)]
pub blend_mode: Vec<String>,
#[serde(default)]
pub layer_kind: Vec<String>,
#[serde(default)]
pub modifier: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ManifestVeil {
#[serde(flatten)]
pub instance: InstancePayload,
pub visible: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManifestPixelRef {
pub format: String,
pub pixels: String,
pub bounds: CanvasRect,
}
pub struct SaveBundle {
pub manifest_json: Vec<u8>,
pub composite_width: u32,
pub composite_height: u32,
pub composite_rgba: Vec<u8>,
pub blobs: Vec<SaveBlob>,
}
pub struct SaveBlob {
pub path: String,
pub bytes: Vec<u8>,
}
pub fn texture_format_to_str(format: wgpu::TextureFormat) -> &'static str {
match format {
wgpu::TextureFormat::Rgba8Unorm => "rgba8unorm",
wgpu::TextureFormat::R8Unorm => "r8unorm",
other => panic!("texture_format_to_str: unrepresentable format {other:?}"),
}
}
pub fn texture_format_from_str(slug: &str) -> Option<wgpu::TextureFormat> {
match slug {
"rgba8unorm" => Some(wgpu::TextureFormat::Rgba8Unorm),
"r8unorm" => Some(wgpu::TextureFormat::R8Unorm),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writer_version_is_crate_version() {
assert_eq!(ManifestWriter::current().version, crate::VERSION);
}
#[test]
fn texture_format_slug_round_trip() {
for fmt in [
wgpu::TextureFormat::Rgba8Unorm,
wgpu::TextureFormat::R8Unorm,
] {
let slug = texture_format_to_str(fmt);
assert_eq!(texture_format_from_str(slug), Some(fmt));
}
assert!(texture_format_from_str("future_format").is_none());
}
#[test]
#[should_panic(expected = "unrepresentable format")]
fn texture_format_to_str_panics_on_unknown() {
let _ = texture_format_to_str(wgpu::TextureFormat::Rgba16Float);
}
#[test]
fn full_manifest_round_trip() {
let m = Manifest {
format: FORMAT_TAG.into(),
container_version: CONTAINER_VERSION,
writer: ManifestWriter::current(),
name: "Untitled".into(),
canvas: ManifestCanvas {
width: 2048,
height: 2048,
origin_x: 0,
origin_y: 0,
},
requires: ManifestRequires {
veil: vec!["grain".into()],
blend_mode: vec!["normal".into(), "multiply".into()],
layer_kind: vec!["raster".into(), "group".into()],
modifier: vec!["mask".into()],
},
composite: "composite.png".into(),
root: 0,
nodes: vec![ManifestEntry {
id: 0,
type_id: "group".into(),
body: serde_json::json!({
"name": "Root",
"visible": true,
"locked": false,
"opacity": 1.0,
"blend_mode": "normal",
"passthrough": true,
"collapsed": false,
"children": [],
"modifiers": [],
}),
}],
modifiers: vec![],
selection_id: None,
veils: vec![ManifestVeil {
instance: InstancePayload::new(
"grain",
vec![
crate::gpu::params::ParamValue::Float(0.5),
crate::gpu::params::ParamValue::Float(0.05),
crate::gpu::params::ParamValue::Float(1.0),
],
),
visible: true,
}],
};
let json = serde_json::to_string(&m).unwrap();
let back: Manifest = serde_json::from_str(&json).unwrap();
assert_eq!(back, m);
}
}