use crate::application_io::TextureFrame; use crate::raster::{BlendMode, ImageFrame}; use crate::transform::{ApplyTransform, Footprint, Transform, TransformMut}; use crate::uuid::NodeId; use crate::vector::VectorData; use crate::Color; use dyn_any::DynAny; use core::ops::{Deref, DerefMut}; use glam::{DAffine2, IVec2}; 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, } } } /// A list of [`GraphicElement`]s #[derive(Clone, Debug, PartialEq, DynAny, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct GraphicGroup { elements: Vec<(GraphicElement, Option)>, pub transform: DAffine2, pub alpha_blending: AlphaBlending, } impl core::hash::Hash for GraphicGroup { fn hash(&self, state: &mut H) { self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state)); self.elements.hash(state); self.alpha_blending.hash(state); } } impl GraphicGroup { pub const EMPTY: Self = Self { elements: Vec::new(), transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::new(), }; pub fn new(elements: Vec) -> Self { Self { elements: elements.into_iter().map(|element| (element, None)).collect(), transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::new(), } } } /// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`]. /// Can be another recursively nested [`GraphicGroup`], a [`VectorData`] shape, an [`ImageFrame`], or an [`Artboard`]. #[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(GraphicGroup), /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path VectorData(Box), Raster(Raster), } // 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(Box::new(VectorData::empty())) } } impl GraphicElement { pub fn as_group(&self) -> Option<&GraphicGroup> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } pub fn as_group_mut(&mut self) -> Option<&mut GraphicGroup> { match self { GraphicElement::GraphicGroup(group) => Some(group), _ => None, } } pub fn as_vector_data(&self) -> Option<&VectorData> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } pub fn as_vector_data_mut(&mut self) -> Option<&mut VectorData> { match self { GraphicElement::VectorData(data) => Some(data), _ => None, } } pub fn as_raster(&self) -> Option<&Raster> { match self { GraphicElement::Raster(raster) => Some(raster), _ => None, } } pub fn as_raster_mut(&mut self) -> Option<&mut Raster> { match self { GraphicElement::Raster(raster) => Some(raster), _ => None, } } } #[derive(Clone, Debug, Hash, PartialEq, DynAny)] pub enum Raster { /// A bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image ImageFrame(ImageFrame), Texture(TextureFrame), } impl<'de> serde::Deserialize<'de> for Raster { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let frame = ImageFrame::deserialize(deserializer)?; Ok(Raster::ImageFrame(frame)) } } impl serde::Serialize for Raster { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { Raster::ImageFrame(_) => self.serialize(serializer), Raster::Texture(_) => todo!(), } } } impl Transform for Raster { fn transform(&self) -> DAffine2 { match self { Raster::ImageFrame(frame) => frame.transform(), Raster::Texture(frame) => frame.transform(), } } fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 { match self { Raster::ImageFrame(frame) => frame.local_pivot(pivot), Raster::Texture(frame) => frame.local_pivot(pivot), } } } impl TransformMut for Raster { fn transform_mut(&mut self) -> &mut DAffine2 { match self { Raster::ImageFrame(frame) => frame.transform_mut(), Raster::Texture(frame) => frame.transform_mut(), } } } /// Some [`ArtboardData`] with some optional clipping bounds that can be exported. /// Similar to an Inkscape page: https://media.inkscape.org/media/doc/release_notes/1.2/Inkscape_1.2.html#Page_tool #[derive(Clone, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Artboard { pub graphic_group: GraphicGroup, pub label: String, pub location: IVec2, pub dimensions: IVec2, pub background: Color, pub clip: bool, } impl Artboard { pub fn new(location: IVec2, dimensions: IVec2) -> Self { Self { graphic_group: GraphicGroup::EMPTY, label: String::from("Artboard"), location: location.min(location + dimensions), dimensions: dimensions.abs(), background: Color::WHITE, clip: false, } } } /// Contains multiple artboards. #[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ArtboardGroup { pub artboards: Vec<(Artboard, Option)>, } impl ArtboardGroup { pub const EMPTY: Self = Self { artboards: Vec::new() }; pub fn new() -> Self { Default::default() } fn append_artboard(&mut self, artboard: Artboard, node_id: Option) { self.artboards.push((artboard, node_id)); } } #[node_macro::node(category(""))] async fn layer( #[implementations( (), Footprint, )] footprint: F, #[implementations( () -> GraphicGroup, Footprint -> GraphicGroup, )] stack: impl Node, #[implementations( () -> GraphicElement, Footprint -> GraphicElement, )] element: impl Node, node_path: Vec, ) -> GraphicGroup { let mut element = element.eval(footprint).await; let mut stack = stack.eval(footprint).await; if stack.transform.matrix2.determinant() != 0. { *element.transform_mut() = stack.transform.inverse() * element.transform(); } else { stack.clear(); stack.transform = DAffine2::IDENTITY; } // Get the penultimate element of the node path, or None if the path is too short let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); stack.push((element, encapsulating_node_id)); stack } #[node_macro::node(category("Debug"))] async fn to_element + 'n>( #[implementations( (), (), (), (), Footprint, )] footprint: F, #[implementations( () -> GraphicGroup, () -> VectorData, () -> ImageFrame, () -> TextureFrame, Footprint -> GraphicGroup, Footprint -> VectorData, Footprint -> ImageFrame, Footprint -> TextureFrame, )] data: impl Node, ) -> GraphicElement { data.eval(footprint).await.into() } #[node_macro::node(category("General"))] async fn to_group + 'n>( #[implementations( (), (), (), (), Footprint, )] footprint: F, #[implementations( () -> GraphicGroup, () -> VectorData, () -> ImageFrame, () -> TextureFrame, Footprint -> GraphicGroup, Footprint -> VectorData, Footprint -> ImageFrame, Footprint -> TextureFrame, )] element: impl Node, ) -> GraphicGroup { element.eval(footprint).await.into() } #[node_macro::node(category(""))] async fn to_artboard + 'n>( #[implementations( (), (), (), (), Footprint, )] mut footprint: F, #[implementations( () -> GraphicGroup, () -> VectorData, () -> ImageFrame, () -> TextureFrame, Footprint -> GraphicGroup, Footprint -> VectorData, Footprint -> ImageFrame, Footprint -> TextureFrame, )] contents: impl Node, label: String, location: IVec2, dimensions: IVec2, background: Color, clip: bool, ) -> Artboard { footprint.apply_transform(&DAffine2::from_translation(location.as_dvec2())); let graphic_group = contents.eval(footprint).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( #[implementations( (), Footprint, )] footprint: F, #[implementations( () -> ArtboardGroup, Footprint -> ArtboardGroup, )] artboards: impl Node, #[implementations( () -> Artboard, Footprint -> Artboard, )] artboard: impl Node, node_path: Vec, ) -> ArtboardGroup { let artboard = artboard.eval(footprint).await; let mut artboards = artboards.eval(footprint).await; // Get the penultimate element of the node path, or None if the path is too short let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); artboards.append_artboard(artboard, encapsulating_node_id); artboards } impl From> for GraphicElement { fn from(image_frame: ImageFrame) -> Self { GraphicElement::Raster(Raster::ImageFrame(image_frame)) } } impl From for GraphicElement { fn from(texture: TextureFrame) -> Self { GraphicElement::Raster(Raster::Texture(texture)) } } impl From for GraphicElement { fn from(vector_data: VectorData) -> Self { GraphicElement::VectorData(Box::new(vector_data)) } } impl From for GraphicElement { fn from(graphic_group: GraphicGroup) -> Self { GraphicElement::GraphicGroup(graphic_group) } } impl Deref for GraphicGroup { type Target = Vec<(GraphicElement, Option)>; fn deref(&self) -> &Self::Target { &self.elements } } impl DerefMut for GraphicGroup { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.elements } } /// This is a helper trait used for the Into Implementation. /// We can't just implement this for all for which from is implemented /// as that would conflict with the implementation for `Self` trait ToGraphicElement: Into {} impl ToGraphicElement for VectorData {} impl ToGraphicElement for ImageFrame {} impl ToGraphicElement for TextureFrame {} impl From for GraphicGroup where T: ToGraphicElement, { fn from(value: T) -> Self { Self { elements: (vec![(value.into(), None)]), transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), } } }