use crate::application_io::{ImageTexture, TextureFrameTable}; use crate::instances::Instances; use crate::raster::BlendMode; use crate::raster::image::{Image, ImageFrameTable}; use crate::transform::TransformMut; use crate::uuid::NodeId; use crate::vector::{VectorData, VectorDataTable}; use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use dyn_any::DynAny; use glam::{DAffine2, IVec2}; use std::hash::Hash; pub mod renderer; #[derive(Copy, Clone, Debug, PartialEq, DynAny, specta::Type)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct AlphaBlending { pub opacity: f32, pub blend_mode: BlendMode, } impl Default for AlphaBlending { fn default() -> Self { Self::new() } } impl core::hash::Hash for AlphaBlending { fn hash(&self, state: &mut H) { self.opacity.to_bits().hash(state); self.blend_mode.hash(state); } } impl AlphaBlending { pub const fn new() -> Self { Self { opacity: 1., blend_mode: BlendMode::Normal, } } } // TODO: Eventually remove this migration document upgrade code pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use serde::Deserialize; #[derive(Clone, Debug, PartialEq, DynAny, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct OldGraphicGroup { elements: Vec<(GraphicElement, Option)>, transform: DAffine2, alpha_blending: AlphaBlending, } #[derive(Clone, Debug, PartialEq, DynAny, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GraphicGroup { elements: Vec<(GraphicElement, Option)>, } pub type OldGraphicGroupTable = Instances; #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum EitherFormat { OldGraphicGroup(OldGraphicGroup), InstanceTable(serde_json::Value), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::OldGraphicGroup(old) => { let mut graphic_group_table = GraphicGroupTable::empty(); for (graphic_element, source_node_id) in old.elements { let last = graphic_group_table.push(graphic_element); *last.source_node_id = source_node_id; *last.transform = old.transform; *last.alpha_blending = old.alpha_blending; } graphic_group_table } EitherFormat::InstanceTable(value) => { // Try to deserialize as either table format if let Ok(old_table) = serde_json::from_value::(value.clone()) { let mut graphic_group_table = GraphicGroupTable::empty(); for instance in old_table.instances() { for (graphic_element, source_node_id) in &instance.instance.elements { let new_row = graphic_group_table.push(graphic_element.clone()); *new_row.source_node_id = *source_node_id; *new_row.transform = *instance.transform; *new_row.alpha_blending = *instance.alpha_blending; } } graphic_group_table } else if let Ok(new_table) = serde_json::from_value::(value) { new_table } else { return Err(serde::de::Error::custom("Failed to deserialize GraphicGroupTable")); } } }) } // TODO: Rename to GraphicElementTable pub type GraphicGroupTable = Instances; impl From for GraphicGroupTable { fn from(vector_data: VectorData) -> Self { Self::new(GraphicElement::VectorData(VectorDataTable::new(vector_data))) } } impl From for GraphicGroupTable { fn from(vector_data: VectorDataTable) -> Self { Self::new(GraphicElement::VectorData(vector_data)) } } impl From> for GraphicGroupTable { fn from(image: Image) -> Self { Self::new(GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image)))) } } impl From> for GraphicGroupTable { fn from(image_frame: ImageFrameTable) -> Self { Self::new(GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame))) } } impl From for GraphicGroupTable { fn from(image_texture: ImageTexture) -> Self { Self::new(GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(image_texture)))) } } impl From for GraphicGroupTable { fn from(texture_frame: TextureFrameTable) -> Self { Self::new(GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture_frame))) } } /// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`]. #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum GraphicElement { /// Equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g GraphicGroup(GraphicGroupTable), /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path VectorData(VectorDataTable), RasterFrame(RasterFrame), } // TODO: Can this be removed? It doesn't necessarily make that much sense to have a default when, instead, the entire GraphicElement just shouldn't exist if there's no specific content to assign it. impl Default for GraphicElement { fn default() -> Self { Self::VectorData(VectorDataTable::default()) } } impl GraphicElement { pub fn as_group(&self) -> Option<&GraphicGroupTable> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroupTable> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } pub fn as_vector_data(&self) -> Option<&VectorDataTable> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorDataTable> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } pub fn as_raster(&self) -> Option<&RasterFrame> { match self { GraphicElement::RasterFrame(raster) => Some(raster), _ => None, } } pub fn as_raster_mut(&mut self) -> Option<&mut RasterFrame> { match self { GraphicElement::RasterFrame(raster) => Some(raster), _ => None, } } } // TODO: Rename to Raster #[derive(Clone, Debug, Hash, PartialEq, DynAny)] pub enum RasterFrame { /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image // TODO: Rename to ImageTable ImageFrame(ImageFrameTable), /// A GPU texture with a finite position and extent // TODO: Rename to ImageTextureTable TextureFrame(TextureFrameTable), } impl<'de> serde::Deserialize<'de> for RasterFrame { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { Ok(RasterFrame::ImageFrame(ImageFrameTable::new(Image::deserialize(deserializer)?))) } } impl serde::Serialize for RasterFrame { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { RasterFrame::ImageFrame(_) => self.serialize(serializer), RasterFrame::TextureFrame(_) => todo!(), } } } /// Some [`ArtboardData`] with some optional clipping bounds that can be exported. #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Artboard { pub graphic_group: GraphicGroupTable, pub label: String, pub location: IVec2, pub dimensions: IVec2, pub background: Color, pub clip: bool, } impl Default for Artboard { fn default() -> Self { Self::new(IVec2::ZERO, IVec2::new(1920, 1080)) } } impl Artboard { pub fn new(location: IVec2, dimensions: IVec2) -> Self { Self { graphic_group: GraphicGroupTable::default(), label: "Artboard".to_string(), location: location.min(location + dimensions), dimensions: dimensions.abs(), background: Color::WHITE, clip: false, } } } // TODO: Eventually remove this migration document upgrade code pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { use serde::Deserialize; #[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ArtboardGroup { pub artboards: Vec<(Artboard, Option)>, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum EitherFormat { ArtboardGroup(ArtboardGroup), ArtboardGroupTable(ArtboardGroupTable), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::ArtboardGroup(artboard_group) => { let mut table = ArtboardGroupTable::empty(); for (artboard, source_node_id) in artboard_group.artboards { let pushed = table.push(artboard); *pushed.source_node_id = source_node_id; } table } EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table, }) } pub type ArtboardGroupTable = Instances; #[node_macro::node(category(""))] async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElement, node_path: Vec) -> GraphicGroupTable { // Get the penultimate element of the node path, or None if the path is too short let pushed = stack.push(element); *pushed.source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); stack } // // TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General" // #[node_macro::node(category("Debug"))] // async fn concatenate( // _: impl Ctx, // #[implementations( // GraphicGroupTable, // VectorDataTable, // ImageFrameTable, // TextureFrameTable, // )] // from: Instances, // #[expose] // #[implementations( // GraphicGroupTable, // VectorDataTable, // ImageFrameTable, // TextureFrameTable, // )] // mut to: Instances, // ) -> Instances { // for instance in from.instances() { // to.push_instance(instance); // } // to // } #[node_macro::node(category("Debug"))] async fn to_element + 'n>( _: impl Ctx, #[implementations( GraphicGroupTable, VectorDataTable, ImageFrameTable, TextureFrameTable, )] data: Data, ) -> GraphicElement { data.into() } #[node_macro::node(category("General"))] async fn to_group + 'n>( _: impl Ctx, #[implementations( GraphicGroupTable, VectorDataTable, ImageFrameTable, TextureFrameTable, )] element: Data, ) -> GraphicGroupTable { element.into() } #[node_macro::node(category("General"))] async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable { // TODO: Avoid mutable reference, instead return a new GraphicGroupTable? fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) { for current_instance in current_group_table.instances() { let current_element = current_instance.instance.clone(); let reference = *current_instance.source_node_id; let recurse = fully_flatten || recursion_depth == 0; match current_element { // If we're allowed to recurse, flatten any GraphicGroups we encounter GraphicElement::GraphicGroup(mut current_element) if recurse => { // Apply the parent group's transform to all child elements for graphic_element in current_element.instances_mut() { *graphic_element.transform = *current_instance.transform * *graphic_element.transform; } flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1); } // Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten _ => { let pushed = output_group_table.push(current_element); *pushed.source_node_id = reference; // Apply the parent group's transform to the leaf element *pushed.transform = *current_instance.transform * *pushed.transform; } } } } let mut output = GraphicGroupTable::default(); flatten_group(&mut output, group, fully_flatten, 0); output } #[node_macro::node(category(""))] async fn to_artboard + 'n>( ctx: impl ExtractAll + CloneVarArgs + Ctx, #[implementations( Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable, Context -> TextureFrameTable, )] contents: impl Node, Output = Data>, label: String, location: IVec2, dimensions: IVec2, background: Color, clip: bool, ) -> Artboard { let footprint = ctx.try_footprint().copied(); let mut new_ctx = OwnedContextImpl::from(ctx); if let Some(mut footprint) = footprint { footprint.translate(location.as_dvec2()); new_ctx = new_ctx.with_footprint(footprint); } let graphic_group = contents.eval(new_ctx.into_context()).await; Artboard { graphic_group: graphic_group.into(), label, location: location.min(location + dimensions), dimensions: dimensions.abs(), background, clip, } } #[node_macro::node(category(""))] async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec) -> ArtboardGroupTable { // Get the penultimate element of the node path, or None if the path is too short. // This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node). let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); let pushed = artboards.push(artboard); *pushed.source_node_id = encapsulating_node_id; artboards } // TODO: Remove this one impl From> for GraphicElement { fn from(image_frame: Image) -> Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(ImageFrameTable::new(image_frame))) } } impl From> for GraphicElement { fn from(image_frame: ImageFrameTable) -> Self { GraphicElement::RasterFrame(RasterFrame::ImageFrame(image_frame)) } } // TODO: Remove this one impl From for GraphicElement { fn from(texture: ImageTexture) -> Self { GraphicElement::RasterFrame(RasterFrame::TextureFrame(TextureFrameTable::new(texture))) } } impl From for GraphicElement { fn from(texture: TextureFrameTable) -> Self { GraphicElement::RasterFrame(RasterFrame::TextureFrame(texture)) } } // TODO: Remove this one impl From for GraphicElement { fn from(vector_data: VectorData) -> Self { GraphicElement::VectorData(VectorDataTable::new(vector_data)) } } impl From for GraphicElement { fn from(vector_data: VectorDataTable) -> Self { GraphicElement::VectorData(vector_data) } } impl From for GraphicElement { fn from(graphic_group: GraphicGroupTable) -> Self { GraphicElement::GraphicGroup(graphic_group) } }