use crate::blending::AlphaBlending; use crate::bounds::BoundingBox; use crate::math::quad::Quad; use crate::raster_types::{CPU, GPU, Raster}; use crate::table::{Table, TableRow}; use crate::uuid::NodeId; use crate::vector::Vector; use crate::{Color, Ctx}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use std::hash::Hash; /// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax. #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub enum Graphic { Group(Table), Vector(Table), RasterCPU(Table>), RasterGPU(Table>), } impl Default for Graphic { fn default() -> Self { Self::Group(Default::default()) } } // Group impl From> for Graphic { fn from(group: Table) -> Self { Graphic::Group(group) } } // Vector impl From for Graphic { fn from(vector: Vector) -> Self { Graphic::Vector(Table::new_from_element(vector)) } } impl From> for Graphic { fn from(vector: Table) -> Self { Graphic::Vector(vector) } } impl From for Table { fn from(vector: Vector) -> Self { Table::new_from_element(Graphic::Vector(Table::new_from_element(vector))) } } impl From> for Table { fn from(vector: Table) -> Self { Table::new_from_element(Graphic::Vector(vector)) } } // Raster impl From> for Graphic { fn from(raster: Raster) -> Self { Graphic::RasterCPU(Table::new_from_element(raster)) } } impl From>> for Graphic { fn from(raster: Table>) -> Self { Graphic::RasterCPU(raster) } } impl From> for Table { fn from(raster: Raster) -> Self { Table::new_from_element(Graphic::RasterCPU(Table::new_from_element(raster))) } } impl From>> for Table { fn from(raster: Table>) -> Self { Table::new_from_element(Graphic::RasterCPU(raster)) } } // Raster impl From> for Graphic { fn from(raster: Raster) -> Self { Graphic::RasterGPU(Table::new_from_element(raster)) } } impl From>> for Graphic { fn from(raster: Table>) -> Self { Graphic::RasterGPU(raster) } } impl From> for Table { fn from(raster: Raster) -> Self { Table::new_from_element(Graphic::RasterGPU(Table::new_from_element(raster))) } } impl From>> for Table { fn from(raster: Table>) -> Self { Table::new_from_element(Graphic::RasterGPU(raster)) } } // DAffine2 impl From for Graphic { fn from(_: DAffine2) -> Self { Graphic::default() } } impl From for Table { fn from(_: DAffine2) -> Self { Table::new() } } impl Graphic { pub fn as_group(&self) -> Option<&Table> { match self { Graphic::Group(group) => Some(group), _ => None, } } pub fn as_group_mut(&mut self) -> Option<&mut Table> { match self { Graphic::Group(group) => Some(group), _ => None, } } pub fn as_vector(&self) -> Option<&Table> { match self { Graphic::Vector(vector) => Some(vector), _ => None, } } pub fn as_vector_mut(&mut self) -> Option<&mut Table> { match self { Graphic::Vector(vector) => Some(vector), _ => None, } } pub fn as_raster(&self) -> Option<&Table>> { match self { Graphic::RasterCPU(raster) => Some(raster), _ => None, } } pub fn as_raster_mut(&mut self) -> Option<&mut Table>> { match self { Graphic::RasterCPU(raster) => Some(raster), _ => None, } } pub fn had_clip_enabled(&self) -> bool { match self { Graphic::Vector(vector) => vector.iter().all(|row| row.alpha_blending.clip), Graphic::Group(group) => group.iter().all(|row| row.alpha_blending.clip), Graphic::RasterCPU(raster) => raster.iter().all(|row| row.alpha_blending.clip), Graphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip), } } pub fn can_reduce_to_clip_path(&self) -> bool { match self { Graphic::Vector(vector) => vector.iter().all(|row| { let style = &row.element.style; let alpha_blending = &row.alpha_blending; (alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke()) }), _ => false, } } } impl BoundingBox for Graphic { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> { match self { Graphic::Vector(vector) => vector.bounding_box(transform, include_stroke), Graphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke), Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke), Graphic::Group(group) => group.bounding_box(transform, include_stroke), } } } impl BoundingBox for Table { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> { self.iter() .filter_map(|element| element.element.bounding_box(transform * *element.transform, include_stroke)) .reduce(Quad::combine_bounds) } } #[node_macro::node(category(""))] async fn layer( _: impl Ctx, #[implementations(Table, Table, Table>, Table>)] mut stack: Table, #[implementations(Graphic, Vector, Raster, Raster)] element: I, node_path: Vec, ) -> Table { // Get the penultimate element of the node path, or None if the path is too short let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied(); stack.push(TableRow { element, transform: DAffine2::IDENTITY, alpha_blending: AlphaBlending::default(), source_node_id, }); stack } #[node_macro::node(category("Debug"))] async fn to_element + 'n>( _: impl Ctx, #[implementations( Table, Table, Table>, Table>, DAffine2, )] data: Data, ) -> Graphic { data.into() } #[node_macro::node(category("General"))] async fn to_group> + 'n>( _: impl Ctx, #[implementations( Table, Table, Table>, Table>, )] element: Data, ) -> Table { element.into() } #[node_macro::node(category("General"))] async fn flatten_group(_: impl Ctx, group: Table, fully_flatten: bool) -> Table { // TODO: Avoid mutable reference, instead return a new Table? fn flatten_group(output_group_table: &mut Table, current_group_table: Table, fully_flatten: bool, recursion_depth: usize) { for current_row in current_group_table.iter() { let current_element = current_row.element.clone(); let reference = *current_row.source_node_id; let recurse = fully_flatten || recursion_depth == 0; match current_element { // If we're allowed to recurse, flatten any groups we encounter Graphic::Group(mut current_element) if recurse => { // Apply the parent group's transform to all child elements for graphic in current_element.iter_mut() { *graphic.transform = *current_row.transform * *graphic.transform; } flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1); } // Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten _ => { output_group_table.push(TableRow { element: current_element, transform: *current_row.transform, alpha_blending: *current_row.alpha_blending, source_node_id: reference, }); } } } } let mut output = Table::new(); flatten_group(&mut output, group, fully_flatten, 0); output } #[node_macro::node(category("Vector"))] async fn flatten_vector(_: impl Ctx, group: Table) -> Table { // TODO: Avoid mutable reference, instead return a new Table? fn flatten_group(output_group_table: &mut Table, current_group_table: Table) { for current_graphic_row in current_group_table.iter() { let current_graphic = current_graphic_row.element.clone(); let source_node_id = *current_graphic_row.source_node_id; match current_graphic { // If we're allowed to recurse, flatten any groups we encounter Graphic::Group(mut current_graphic_table) => { // Apply the parent group's transform to all child elements for graphic in current_graphic_table.iter_mut() { *graphic.transform = *current_graphic_row.transform * *graphic.transform; } flatten_group(output_group_table, current_graphic_table); } // Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten Graphic::Vector(vector_table) => { for current_vector_row in vector_table.iter() { output_group_table.push(TableRow { element: current_vector_row.element.clone(), transform: *current_graphic_row.transform * *current_vector_row.transform, alpha_blending: AlphaBlending { blend_mode: current_vector_row.alpha_blending.blend_mode, opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity, fill: current_vector_row.alpha_blending.fill, clip: current_vector_row.alpha_blending.clip, }, source_node_id, }); } } _ => {} } } } let mut output = Table::new(); flatten_group(&mut output, group); output } /// Returns the value at the specified index in the collection. /// If that index has no value, the type's default value is returned. #[node_macro::node(category("General"))] fn index( _: impl Ctx, /// The collection of data, such as a list or table. #[implementations( Vec, Vec>, Vec, Vec, Vec, Table, Table>, Table, )] collection: T, /// The index of the item to retrieve, starting from 0 for the first item. index: u32, ) -> T::Output where T::Output: Clone + Default, { collection.at_index(index as usize).unwrap_or_default() } pub trait AtIndex { type Output; fn at_index(&self, index: usize) -> Option; } impl AtIndex for Vec { type Output = T; fn at_index(&self, index: usize) -> Option { self.get(index).cloned() } } impl AtIndex for Table { type Output = Table; fn at_index(&self, index: usize) -> Option { let mut result_table = Self::default(); if let Some(row) = self.iter().nth(index) { result_table.push(row.into_cloned()); Some(result_table) } else { None } } } // TODO: Eventually remove this migration document upgrade code pub fn migrate_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; #[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)] pub struct OldGraphicGroup { elements: Vec<(Graphic, Option)>, transform: DAffine2, alpha_blending: AlphaBlending, } #[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)] pub struct GraphicGroup { elements: Vec<(Graphic, Option)>, } #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] enum EitherFormat { OldGraphicGroup(OldGraphicGroup), Table(serde_json::Value), } Ok(match EitherFormat::deserialize(deserializer)? { EitherFormat::OldGraphicGroup(old) => { let mut group_table = Table::new(); for (graphic, source_node_id) in old.elements { group_table.push(TableRow { element: graphic, transform: old.transform, alpha_blending: old.alpha_blending, source_node_id, }); } group_table } EitherFormat::Table(value) => { // Try to deserialize as either table format if let Ok(old_table) = serde_json::from_value::>(value.clone()) { let mut group_table = Table::new(); for row in old_table.iter() { for (graphic, source_node_id) in &row.element.elements { group_table.push(TableRow { element: graphic.clone(), transform: *row.transform, alpha_blending: *row.alpha_blending, source_node_id: *source_node_id, }); } } 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 Table")); } } }) }