Instance tables refactor part 4: replace ArtboardGroups with multi-row Instances<Artboard> (#2265)

* Clean up dyn_any usages

* Migrate ArtboardGroup to ArtboardGroupTable (not yet flattened)

* Reorder graphical data imports

* Flatten and remove ArtboardGroup in favor of ArtboardGroupTable

* Fix test
This commit is contained in:
Keavon Chambers 2025-03-02 17:30:29 -08:00
parent 2f6c6e28f0
commit 19a140682e
32 changed files with 233 additions and 156 deletions

View file

@ -2414,7 +2414,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export /// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface { fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default(); let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::default()), -1, "", &[]); network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroupTable::default()), -1, "", &[]);
network_interface network_interface
} }

View file

@ -126,7 +126,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network /// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier { pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([ let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::default()), true)), Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroupTable::default()), true)), Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)), Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)), Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),

View file

@ -295,7 +295,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![ inputs: vec![
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroup))), 0), NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(ArtboardGroupTable))), 0),
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
@ -310,7 +310,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroup::default()), true), NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false), NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false), NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
@ -544,7 +544,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)], inputs: vec![NodeInput::network(concrete!(ImageFrameTable<Color>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrame>")), // TODO: Possibly change `ImageFrame` to something else implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, ImageFrameTable>")),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
@ -571,7 +571,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -809,8 +809,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::MaskImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -832,8 +832,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::InsertChannelNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false), NodeInput::value(TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
], ],
..Default::default() ..Default::default()
@ -856,10 +856,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"), implementation: DocumentNodeImplementation::proto("graphene_std::raster::CombineChannelsNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
], ],
..Default::default() ..Default::default()
}, },
@ -929,7 +929,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1011,8 +1011,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
], ],
@ -1061,7 +1061,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@ -1080,7 +1080,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@ -1112,7 +1112,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), false)], inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), false),
],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1825,7 +1828,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(), .collect(),
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true)], inputs: vec![NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true)],
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1881,7 +1884,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"), implementation: DocumentNodeImplementation::proto("graphene_std::executor::MapGpuSingleImageNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true), NodeInput::value(TaggedValue::DocumentNode(DocumentNode::default()), true),
], ],
..Default::default() ..Default::default()
@ -1923,7 +1926,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"), implementation: DocumentNodeImplementation::proto("graphene_core::raster::BrightnessContrastNode"),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false), NodeInput::value(TaggedValue::Bool(false), false),
@ -2799,7 +2802,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentN
..Default::default() ..Default::default()
}), }),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::empty()), true), NodeInput::value(TaggedValue::ImageFrame(ImageFrameTable::one_empty_image()), true),
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
NodeInput::value(TaggedValue::ImaginateController(Default::default()), false), NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index

View file

@ -468,8 +468,8 @@ impl NodeNetworkInterface {
InputConnector::Node { node_id, input_index } => (node_id, input_index), InputConnector::Node { node_id, input_index } => (node_id, input_index),
InputConnector::Export(export_index) => { InputConnector::Export(export_index) => {
let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else { let Some((encapsulating_node_id, encapsulating_node_id_path)) = network_path.split_last() else {
// The outermost network export defaults to an ArtboardGroup. // The outermost network export defaults to an ArtboardGroupTable.
return Some((concrete!(graphene_core::ArtboardGroup), TypeSource::OuterMostExportDefault)); return Some((concrete!(graphene_core::ArtboardGroupTable), TypeSource::OuterMostExportDefault));
}; };
let output_type = self.output_types(encapsulating_node_id, encapsulating_node_id_path).into_iter().nth(export_index).flatten(); let output_type = self.output_types(encapsulating_node_id, encapsulating_node_id_path).into_iter().nth(export_index).flatten();

View file

@ -49,6 +49,7 @@ impl TransformMut for SurfaceFrame {
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl StaticType for SurfaceFrame { unsafe impl StaticType for SurfaceFrame {
type Static = SurfaceFrame; type Static = SurfaceFrame;
} }
@ -90,6 +91,7 @@ impl PartialEq for ImageTexture {
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl StaticType for ImageTexture { unsafe impl StaticType for ImageTexture {
type Static = ImageTexture; type Static = ImageTexture;
} }
@ -128,6 +130,7 @@ impl<S: Size> Size for SurfaceHandle<S> {
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl<T: 'static> StaticType for SurfaceHandle<T> { unsafe impl<T: 'static> StaticType for SurfaceHandle<T> {
type Static = SurfaceHandle<T>; type Static = SurfaceHandle<T>;
} }
@ -138,6 +141,7 @@ pub struct SurfaceHandleFrame<Surface> {
pub transform: DAffine2, pub transform: DAffine2,
} }
#[cfg(feature = "dyn-any")]
unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> { unsafe impl<T: 'static> StaticType for SurfaceHandleFrame<T> {
type Static = SurfaceHandleFrame<T>; type Static = SurfaceHandleFrame<T>;
} }
@ -317,6 +321,7 @@ impl<T> Debug for EditorApi<T> {
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> { unsafe impl<T: StaticTypeSized> StaticType for EditorApi<T> {
type Static = EditorApi<T::Static>; type Static = EditorApi<T::Static>;
} }

View file

@ -238,6 +238,12 @@ pub struct Artboard {
pub clip: bool, pub clip: bool,
} }
impl Default for Artboard {
fn default() -> Self {
Self::new(IVec2::ZERO, IVec2::new(1920, 1080))
}
}
impl Artboard { impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self { pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self { Self {
@ -251,23 +257,38 @@ impl Artboard {
} }
} }
/// Contains multiple artboards. // TODO: Eventually remove this migration document upgrade code
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)] pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ArtboardGroupTable, D::Error> {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] use serde::Deserialize;
pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
}
impl ArtboardGroup { #[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)]
pub fn new() -> Self { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Default::default() pub struct ArtboardGroup {
pub artboards: Vec<(Artboard, Option<NodeId>)>,
} }
fn append_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) { #[derive(serde::Serialize, serde::Deserialize)]
self.artboards.push((artboard, node_id)); #[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 {
table.push(artboard);
*table.instances_mut().last().unwrap().source_node_id = source_node_id;
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
})
} }
pub type ArtboardGroupTable = Instances<Artboard>;
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable { async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
let mut stack = stack; let mut stack = stack;
@ -385,15 +406,13 @@ async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroup, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroup { async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec<NodeId>) -> ArtboardGroupTable {
// let mut artboards = artboards.eval(ctx.clone()).await;
// let artboard = artboard.eval(ctx).await;
// let foot = ctx.footprint();
// log::debug!("{:?}", foot);
// Get the penultimate element of the node path, or None if the path is too short. // 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). // 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 encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
artboards.append_artboard(artboard, encapsulating_node_id);
artboards.push(artboard);
*artboards.instances_mut().last().unwrap().source_node_id = encapsulating_node_id;
artboards artboards
} }

View file

@ -9,7 +9,7 @@ use crate::transform::{Footprint, Transform};
use crate::uuid::{generate_uuid, NodeId}; use crate::uuid::{generate_uuid, NodeId};
use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::{PointId, VectorDataTable}; use crate::vector::{PointId, VectorDataTable};
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroupTable, RasterFrame}; use crate::{Artboard, ArtboardGroupTable, Color, GraphicElement, GraphicGroupTable, RasterFrame};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use dyn_any::DynAny; use dyn_any::DynAny;
@ -790,38 +790,38 @@ impl GraphicElementRendered for Artboard {
} }
} }
impl GraphicElementRendered for ArtboardGroup { impl GraphicElementRendered for ArtboardGroupTable {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for (artboard, _) in &self.artboards { for artboard in self.instances() {
artboard.render_svg(render, render_params); artboard.instance.render_svg(render, render_params);
} }
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.artboards.iter().filter_map(|(element, _)| element.bounding_box(transform)).reduce(Quad::combine_bounds) self.instances().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for (artboard, element_id) in &self.artboards { for instance in self.instances() {
artboard.collect_metadata(metadata, footprint, *element_id); instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id);
} }
} }
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for (artboard, _) in &self.artboards { for instance in self.instances() {
artboard.add_upstream_click_targets(click_targets); instance.instance.add_upstream_click_targets(click_targets);
} }
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
for (artboard, _) in &self.artboards { for instance in self.instances() {
artboard.render_to_vello(scene, transform, context) instance.instance.render_to_vello(scene, transform, context)
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
!self.artboards.is_empty() self.instances().count() > 0
} }
} }

View file

@ -2,18 +2,17 @@ use crate::application_io::{ImageTexture, TextureFrameTable};
use crate::raster::image::{Image, ImageFrameTable}; use crate::raster::image::{Image, ImageFrameTable};
use crate::raster::Pixel; use crate::raster::Pixel;
use crate::transform::{Transform, TransformMut}; use crate::transform::{Transform, TransformMut};
use crate::uuid::NodeId;
use crate::vector::{InstanceId, VectorData, VectorDataTable}; use crate::vector::{InstanceId, VectorData, VectorDataTable};
use crate::{AlphaBlending, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame}; use crate::{AlphaBlending, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
use dyn_any::StaticType; use dyn_any::StaticType;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::hash::Hash; use std::hash::Hash;
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Instances<T> pub struct Instances<T> {
where
T: Into<GraphicElement> + StaticType + 'static,
{
id: Vec<InstanceId>, id: Vec<InstanceId>,
#[serde(alias = "instances")] #[serde(alias = "instances")]
instance: Vec<T>, instance: Vec<T>,
@ -21,15 +20,44 @@ where
transform: Vec<DAffine2>, transform: Vec<DAffine2>,
#[serde(default = "one_alpha_blending_default")] #[serde(default = "one_alpha_blending_default")]
alpha_blending: Vec<AlphaBlending>, alpha_blending: Vec<AlphaBlending>,
#[serde(default = "one_source_node_id_default")]
source_node_id: Vec<Option<NodeId>>,
} }
impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> { impl<T> Instances<T> {
pub fn new(instance: T) -> Self { pub fn new(instance: T) -> Self {
Self { Self {
id: vec![InstanceId::generate()], id: vec![InstanceId::generate()],
instance: vec![instance], instance: vec![instance],
transform: vec![DAffine2::IDENTITY], transform: vec![DAffine2::IDENTITY],
alpha_blending: vec![AlphaBlending::default()], alpha_blending: vec![AlphaBlending::default()],
source_node_id: vec![None],
}
}
pub fn empty() -> Self {
Self {
id: Vec::new(),
instance: Vec::new(),
transform: Vec::new(),
alpha_blending: Vec::new(),
source_node_id: Vec::new(),
}
}
pub fn push(&mut self, instance: T) -> InstanceMut<T> {
self.id.push(InstanceId::generate());
self.instance.push(instance);
self.transform.push(DAffine2::IDENTITY);
self.alpha_blending.push(AlphaBlending::default());
self.source_node_id.push(None);
InstanceMut {
id: self.id.last_mut().expect("Shouldn't be empty"),
instance: self.instance.last_mut().expect("Shouldn't be empty"),
transform: self.transform.last_mut().expect("Shouldn't be empty"),
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
} }
} }
@ -39,6 +67,7 @@ impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())), alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
source_node_id: self.source_node_id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
} }
} }
@ -50,47 +79,52 @@ impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)), alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
source_node_id: self.source_node_id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
} }
} }
pub fn instances(&self) -> impl Iterator<Item = Instance<T>> { pub fn instances(&self) -> impl Iterator<Item = Instance<T>> {
assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instance.len()); // assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instance.len());
self.id self.id
.iter() .iter()
.zip(self.instance.iter()) .zip(self.instance.iter())
.zip(self.transform.iter()) .zip(self.transform.iter())
.zip(self.alpha_blending.iter()) .zip(self.alpha_blending.iter())
.map(|(((id, instance), transform), alpha_blending)| Instance { .zip(self.source_node_id.iter())
.map(|((((id, instance), transform), alpha_blending), source_node_id)| Instance {
id, id,
instance, instance,
transform, transform,
alpha_blending, alpha_blending,
source_node_id,
}) })
} }
pub fn instances_mut(&mut self) -> impl Iterator<Item = InstanceMut<T>> { pub fn instances_mut(&mut self) -> impl Iterator<Item = InstanceMut<T>> {
assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instance.len()); // assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instance.len());
self.id self.id
.iter_mut() .iter_mut()
.zip(self.instance.iter_mut()) .zip(self.instance.iter_mut())
.zip(self.transform.iter_mut()) .zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut()) .zip(self.alpha_blending.iter_mut())
.map(|(((id, instance), transform), alpha_blending)| InstanceMut { .zip(self.source_node_id.iter_mut())
.map(|((((id, instance), transform), alpha_blending), source_node_id)| InstanceMut {
id, id,
instance, instance,
transform, transform,
alpha_blending, alpha_blending,
source_node_id,
}) })
} }
} }
impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> { impl<T: Default + Hash> Default for Instances<T> {
fn default() -> Self { fn default() -> Self {
Self::new(T::default()) Self::new(T::default())
} }
} }
impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> { impl<T: Hash> core::hash::Hash for Instances<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) { fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state); self.id.hash(state);
for instance in &self.instance { for instance in &self.instance {
@ -99,13 +133,14 @@ impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for
} }
} }
impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> { impl<T: PartialEq> PartialEq for Instances<T> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) } self.id == other.id && self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
} }
} }
unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> { #[cfg(feature = "dyn-any")]
unsafe impl<T: StaticType + 'static> StaticType for Instances<T> {
type Static = Instances<T>; type Static = Instances<T>;
} }
@ -115,6 +150,9 @@ fn one_daffine2_default() -> Vec<DAffine2> {
fn one_alpha_blending_default() -> Vec<AlphaBlending> { fn one_alpha_blending_default() -> Vec<AlphaBlending> {
vec![AlphaBlending::default()] vec![AlphaBlending::default()]
} }
fn one_source_node_id_default() -> Vec<Option<NodeId>> {
vec![None]
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub struct Instance<'a, T> { pub struct Instance<'a, T> {
@ -122,6 +160,7 @@ pub struct Instance<'a, T> {
pub instance: &'a T, pub instance: &'a T,
pub transform: &'a DAffine2, pub transform: &'a DAffine2,
pub alpha_blending: &'a AlphaBlending, pub alpha_blending: &'a AlphaBlending,
pub source_node_id: &'a Option<NodeId>,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct InstanceMut<'a, T> { pub struct InstanceMut<'a, T> {
@ -129,6 +168,7 @@ pub struct InstanceMut<'a, T> {
pub instance: &'a mut T, pub instance: &'a mut T,
pub transform: &'a mut DAffine2, pub transform: &'a mut DAffine2,
pub alpha_blending: &'a mut AlphaBlending, pub alpha_blending: &'a mut AlphaBlending,
pub source_node_id: &'a mut Option<NodeId>,
} }
// GRAPHIC ELEMENT // GRAPHIC ELEMENT
@ -235,8 +275,6 @@ impl<P: Pixel> TransformMut for InstanceMut<'_, Image<P>> {
// IMAGE FRAME TABLE // IMAGE FRAME TABLE
impl<P: Pixel> Transform for ImageFrameTable<P> impl<P: Pixel> Transform for ImageFrameTable<P>
where where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
@ -245,8 +283,6 @@ where
} }
impl<P: Pixel> TransformMut for ImageFrameTable<P> impl<P: Pixel> TransformMut for ImageFrameTable<P>
where where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
fn transform_mut(&mut self) -> &mut DAffine2 { fn transform_mut(&mut self) -> &mut DAffine2 {

View file

@ -51,6 +51,8 @@ pub mod registry;
pub use context::*; pub use context::*;
use core::any::TypeId; use core::any::TypeId;
use core::pin::Pin;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use memo::MemoHash; pub use memo::MemoHash;
pub use raster::Color; pub use raster::Color;
pub use types::Cow; pub use types::Cow;
@ -148,10 +150,6 @@ impl<'i, I: 'i, O: 'i, N: Node<'i, I, Output = O> + ?Sized> Node<'i, I> for allo
} }
} }
use dyn_any::StaticTypeSized;
use core::pin::Pin;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> { impl<'i, I, O: 'i> Node<'i, I> for Pin<Box<dyn Node<'i, I, Output = O> + 'i>> {
type Output = O; type Output = O;
@ -172,5 +170,3 @@ pub use crate::application_io::{SurfaceFrame, SurfaceId};
pub type WasmSurfaceHandle = application_io::SurfaceHandle<web_sys::HtmlCanvasElement>; pub type WasmSurfaceHandle = application_io::SurfaceHandle<web_sys::HtmlCanvasElement>;
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
pub type WasmSurfaceHandleFrame = application_io::SurfaceHandleFrame<web_sys::HtmlCanvasElement>; pub type WasmSurfaceHandleFrame = application_io::SurfaceHandleFrame<web_sys::HtmlCanvasElement>;
pub use dyn_any::{WasmNotSend, WasmNotSync};

View file

@ -734,8 +734,6 @@ impl Adjust<Color> for GradientStops {
} }
impl<P: Pixel> Adjust<P> for ImageFrameTable<P> impl<P: Pixel> Adjust<P> for ImageFrameTable<P>
where where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) { fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
@ -1382,8 +1380,6 @@ impl MultiplyAlpha for GraphicGroupTable {
} }
impl<P: Pixel> MultiplyAlpha for ImageFrameTable<P> impl<P: Pixel> MultiplyAlpha for ImageFrameTable<P>
where where
P: dyn_any::StaticType,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
fn multiply_alpha(&mut self, factor: f64) { fn multiply_alpha(&mut self, factor: f64) {

View file

@ -1,4 +1,5 @@
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[cfg_attr(not(target_arch = "spirv"), derive(Debug))] #[cfg_attr(not(target_arch = "spirv"), derive(Debug))]

View file

@ -4,8 +4,9 @@ use crate::vector::brush_stroke::BrushStroke;
use crate::vector::brush_stroke::BrushStyle; use crate::vector::brush_stroke::BrushStyle;
use crate::Color; use crate::Color;
use core::hash::Hash;
use dyn_any::DynAny; use dyn_any::DynAny;
use core::hash::Hash;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@ -57,7 +58,7 @@ impl BrushCacheImpl {
background = core::mem::take(&mut self.blended_image); background = core::mem::take(&mut self.blended_image);
// Check if the first non-blended stroke is an extension of the last one. // Check if the first non-blended stroke is an extension of the last one.
let mut first_stroke_texture = ImageFrameTable::empty(); let mut first_stroke_texture = ImageFrameTable::one_empty_image();
let mut first_stroke_point_skip = 0; let mut first_stroke_point_skip = 0;
let strokes = input[num_blended_strokes..].to_vec(); let strokes = input[num_blended_strokes..].to_vec();
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes { if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {

View file

@ -176,6 +176,7 @@ pub struct ValueMapperNode<C> {
lut: Vec<C>, lut: Vec<C>,
} }
#[cfg(feature = "dyn-any")]
unsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> { unsafe impl<C: StaticTypeSized> StaticType for ValueMapperNode<C> {
type Static = ValueMapperNode<C::Static>; type Static = ValueMapperNode<C::Static>;
} }

View file

@ -1,7 +1,9 @@
use super::discrete_srgb::float_to_srgb_u8; use super::discrete_srgb::float_to_srgb_u8;
use super::Color; use super::Color;
use crate::{instances::Instances, transform::TransformMut}; use crate::instances::Instances;
use crate::{AlphaBlending, GraphicElement}; use crate::transform::TransformMut;
use crate::AlphaBlending;
use crate::GraphicElement;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::hash::{Hash, Hasher}; use core::hash::{Hash, Hasher};
use dyn_any::StaticType; use dyn_any::StaticType;
@ -68,6 +70,7 @@ impl<P: Pixel + Debug> Debug for Image<P> {
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl<P> StaticType for Image<P> unsafe impl<P> StaticType for Image<P>
where where
P: dyn_any::StaticTypeSized + Pixel, P: dyn_any::StaticTypeSized + Pixel,
@ -235,6 +238,8 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
} }
} }
} }
#[cfg(feature = "dyn-any")]
unsafe impl<P> StaticType for ImageFrame<P> unsafe impl<P> StaticType for ImageFrame<P>
where where
P: dyn_any::StaticTypeSized + Pixel, P: dyn_any::StaticTypeSized + Pixel,
@ -279,7 +284,7 @@ pub type ImageFrameTable<P> = Instances<Image<P>>;
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table. /// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
impl ImageFrameTable<Color> { impl ImageFrameTable<Color> {
pub fn empty() -> Self { pub fn one_empty_image() -> Self {
let mut result = Self::new(Image::default()); let mut result = Self::new(Image::default());
*result.transform_mut() = DAffine2::ZERO; *result.transform_mut() = DAffine2::ZERO;
result result
@ -302,8 +307,7 @@ impl<P: Debug + Copy + Pixel> Sample for Image<P> {
impl<P> Sample for ImageFrameTable<P> impl<P> Sample for ImageFrameTable<P>
where where
P: Debug + Copy + Pixel + dyn_any::StaticType, P: Debug + Copy + Pixel,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
type Pixel = P; type Pixel = P;
@ -323,8 +327,7 @@ where
impl<P> Bitmap for ImageFrameTable<P> impl<P> Bitmap for ImageFrameTable<P>
where where
P: Copy + Pixel + dyn_any::StaticType, P: Copy + Pixel,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
type Pixel = P; type Pixel = P;
@ -350,8 +353,7 @@ where
impl<P> BitmapMut for ImageFrameTable<P> impl<P> BitmapMut for ImageFrameTable<P>
where where
P: Copy + Pixel + dyn_any::StaticType, P: Copy + Pixel,
P::Static: Pixel,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> { fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {

View file

@ -1,9 +1,10 @@
use crate::transform::Footprint; use crate::transform::Footprint;
use crate::{NodeIO, NodeIOTypes, Type}; use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend};
use dyn_any::DynAny; use dyn_any::{DynAny, StaticType};
use std::collections::HashMap; use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Deref; use std::ops::Deref;
use std::pin::Pin; use std::pin::Pin;
use std::sync::{LazyLock, Mutex}; use std::sync::{LazyLock, Mutex};
@ -153,11 +154,6 @@ impl NodeContainer {
} }
} }
use crate::Node;
use crate::WasmNotSend;
use dyn_any::StaticType;
use std::marker::PhantomData;
/// Boxes the input and downcasts the output. /// Boxes the input and downcasts the output.
/// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny> /// Wraps around a node taking Box<dyn DynAny> and returning Box<dyn DynAny>
#[derive(Clone)] #[derive(Clone)]
@ -166,7 +162,11 @@ pub struct DowncastBothNode<I, O> {
_i: PhantomData<I>, _i: PhantomData<I>,
_o: PhantomData<O>, _o: PhantomData<O>,
} }
impl<'input, O: 'input + StaticType + WasmNotSend, I: 'input + StaticType + WasmNotSend> Node<'input, I> for DowncastBothNode<I, O> { impl<'input, O, I> Node<'input, I> for DowncastBothNode<I, O>
where
O: 'input + StaticType + WasmNotSend,
I: 'input + StaticType + WasmNotSend,
{
type Output = DynFuture<'input, O>; type Output = DynFuture<'input, O>;
#[inline] #[inline]
fn eval(&'input self, input: I) -> Self::Output { fn eval(&'input self, input: I) -> Self::Output {
@ -234,9 +234,11 @@ pub struct DynAnyNode<I, O, Node> {
_o: PhantomData<O>, _o: PhantomData<O>,
} }
impl<'input, _I: 'input + StaticType + WasmNotSend, _O: 'input + StaticType + WasmNotSend, N: 'input> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N> impl<'input, _I, _O, N> Node<'input, Any<'input>> for DynAnyNode<_I, _O, N>
where where
N: Node<'input, _I, Output = DynFuture<'input, _O>>, _I: 'input + dyn_any::StaticType + WasmNotSend,
_O: 'input + dyn_any::StaticType + WasmNotSend,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
{ {
type Output = FutureAny<'input>; type Output = FutureAny<'input>;
#[inline] #[inline]
@ -275,9 +277,11 @@ where
self.node.serialize() self.node.serialize()
} }
} }
impl<'input, _I: 'input + StaticType, _O: 'input + StaticType, N: 'input> DynAnyNode<_I, _O, N> impl<'input, _I, _O, N> DynAnyNode<_I, _O, N>
where where
N: Node<'input, _I, Output = DynFuture<'input, _O>>, _I: 'input + dyn_any::StaticType,
_O: 'input + dyn_any::StaticType,
N: 'input + Node<'input, _I, Output = DynFuture<'input, _O>>,
{ {
pub const fn new(node: N) -> Self { pub const fn new(node: N) -> Self {
Self { Self {

View file

@ -2,7 +2,7 @@ use crate::application_io::TextureFrameTable;
use crate::raster::bbox::AxisAlignedBbox; use crate::raster::bbox::AxisAlignedBbox;
use crate::raster::image::ImageFrameTable; use crate::raster::image::ImageFrameTable;
use crate::vector::VectorDataTable; use crate::vector::VectorDataTable;
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl}; use crate::{Artboard, ArtboardGroupTable, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
@ -132,7 +132,7 @@ impl From<()> for Footprint {
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
fn cull<T>(_: impl Ctx, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroup)] data: T) -> T { fn cull<T>(_: impl Ctx, #[implementations(VectorDataTable, GraphicGroupTable, Artboard, ImageFrameTable<Color>, ArtboardGroupTable)] data: T) -> T {
data data
} }

View file

@ -2,7 +2,6 @@ use core::any::TypeId;
#[cfg(not(feature = "std"))] #[cfg(not(feature = "std"))]
pub use alloc::borrow::Cow; pub use alloc::borrow::Cow;
use dyn_any::StaticType;
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use std::borrow::Cow; pub use std::borrow::Cow;
@ -220,7 +219,8 @@ impl Default for Type {
} }
} }
unsafe impl StaticType for Type { #[cfg(feature = "dyn-any")]
unsafe impl dyn_any::StaticType for Type {
type Static = Self; type Static = Self;
} }
@ -269,7 +269,7 @@ impl Type {
} }
impl Type { impl Type {
pub fn new<T: StaticType + Sized>() -> Self { pub fn new<T: dyn_any::StaticType + Sized>() -> Self {
Self::Concrete(TypeDescriptor { Self::Concrete(TypeDescriptor {
id: Some(TypeId::of::<T::Static>()), id: Some(TypeId::of::<T::Static>()),
name: Cow::Borrowed(core::any::type_name::<T::Static>()), name: Cow::Borrowed(core::any::type_name::<T::Static>()),
@ -278,6 +278,7 @@ impl Type {
align: core::mem::align_of::<T>(), align: core::mem::align_of::<T>(),
}) })
} }
pub fn size(&self) -> Option<usize> { pub fn size(&self) -> Option<usize> {
match self { match self {
Self::Generic(_) => None, Self::Generic(_) => None,

View file

@ -1,6 +1,7 @@
use dyn_any::DynAny;
pub use uuid_generation::*; pub use uuid_generation::*;
use dyn_any::DynAny;
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Uuid( pub struct Uuid(
#[serde(with = "u64_string")] #[serde(with = "u64_string")]

View file

@ -3,6 +3,7 @@ use crate::raster::BlendMode;
use crate::Color; use crate::Color;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::DVec2; use glam::DVec2;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};

View file

@ -5,6 +5,7 @@ use crate::renderer::format_transform_matrix;
use crate::Color; use crate::Color;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::fmt::{self, Display, Write}; use std::fmt::{self, Display, Write};

View file

@ -670,6 +670,7 @@ async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> V
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))] #[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable { async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
let original_transform = vector_data.transform();
let vector_data = vector_data.one_instance_mut().instance; let vector_data = vector_data.one_instance_mut().instance;
// Exit early if there are no points to generate splines from. // Exit early if there are no points to generate splines from.
@ -707,7 +708,9 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
} }
vector_data.segment_domain = segment_domain; vector_data.segment_domain = segment_domain;
VectorDataTable::new(vector_data.clone()) let mut result = VectorDataTable::new(vector_data.clone());
*result.transform_mut() = original_transform;
result
} }
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]

View file

@ -1,8 +1,8 @@
use dyn_any::{StaticType, StaticTypeSized};
use graphene_core::raster::{color::RGBA16F, Image, Pixel, SRGBA8}; use graphene_core::raster::{color::RGBA16F, Image, Pixel, SRGBA8};
use graphene_core::*; use graphene_core::*;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
use dyn_any::{StaticType, StaticTypeSized};
use glam::UVec3; use glam::UVec3;
use std::borrow::Cow; use std::borrow::Cow;

View file

@ -115,6 +115,19 @@ macro_rules! tagged_value {
} }
tagged_value! { tagged_value! {
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))]
ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))]
VectorData(graphene_core::vector::VectorDataTable),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))]
GraphicGroup(graphene_core::GraphicGroupTable),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_artboard_group"))]
ArtboardGroup(graphene_core::ArtboardGroupTable),
GraphicElement(graphene_core::GraphicElement),
String(String), String(String),
U32(u32), U32(u32),
U64(u64), U64(u64),
@ -130,19 +143,14 @@ tagged_value! {
DAffine2(DAffine2), DAffine2(DAffine2),
Image(graphene_core::raster::Image<Color>), Image(graphene_core::raster::Image<Color>),
ImaginateCache(ImaginateCache), ImaginateCache(ImaginateCache),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))]
ImageFrame(graphene_core::raster::image::ImageFrameTable<Color>),
Color(graphene_core::raster::color::Color), Color(graphene_core::raster::color::Color),
OptionalColor(Option<graphene_core::raster::color::Color>),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>), Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
BlendMode(BlendMode), BlendMode(BlendMode),
LuminanceCalculation(LuminanceCalculation), LuminanceCalculation(LuminanceCalculation),
ImaginateSamplingMethod(ImaginateSamplingMethod), ImaginateSamplingMethod(ImaginateSamplingMethod),
ImaginateMaskStartingFill(ImaginateMaskStartingFill), ImaginateMaskStartingFill(ImaginateMaskStartingFill),
ImaginateController(ImaginateController), ImaginateController(ImaginateController),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))]
VectorData(graphene_core::vector::VectorDataTable),
Fill(graphene_core::vector::style::Fill), Fill(graphene_core::vector::style::Fill),
Stroke(graphene_core::vector::style::Stroke), Stroke(graphene_core::vector::style::Stroke),
F64Array4([f64; 4]), F64Array4([f64; 4]),
@ -170,7 +178,6 @@ tagged_value! {
// TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))] #[cfg_attr(feature = "serde", serde(alias = "GradientPositions"))]
GradientStops(graphene_core::vector::style::GradientStops), GradientStops(graphene_core::vector::style::GradientStops),
OptionalColor(Option<graphene_core::raster::color::Color>),
// TODO: Eventually remove this alias document upgrade code // TODO: Eventually remove this alias document upgrade code
#[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))] #[cfg_attr(feature = "serde", serde(alias = "ManipulatorGroupIds"))]
PointIds(Vec<graphene_core::vector::PointId>), PointIds(Vec<graphene_core::vector::PointId>),
@ -178,11 +185,6 @@ tagged_value! {
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>), BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
BrushCache(BrushCache), BrushCache(BrushCache),
DocumentNode(DocumentNode), DocumentNode(DocumentNode),
// TODO: Eventually remove this migration document upgrade code
#[cfg_attr(feature = "serde", serde(deserialize_with = "graphene_core::migrate_graphic_group"))]
GraphicGroup(graphene_core::GraphicGroupTable),
GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup),
Curve(graphene_core::raster::curve::Curve), Curve(graphene_core::raster::curve::Curve),
Footprint(graphene_core::transform::Footprint), Footprint(graphene_core::transform::Footprint),
Palette(Vec<Color>), Palette(Vec<Color>),

View file

@ -1,11 +1,10 @@
use dyn_any::DynAny; use dyn_any::DynAny;
use graphene_core::Color; use graphene_core::Color;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::{ use std::sync::atomic::{AtomicBool, Ordering};
atomic::{AtomicBool, Ordering}, use std::sync::{Arc, Mutex};
Arc, Mutex,
};
#[derive(Default, Debug, Clone, DynAny, specta::Type)] #[derive(Default, Debug, Clone, DynAny, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]

View file

@ -923,12 +923,12 @@ mod test {
assert_eq!( assert_eq!(
ids, ids,
vec![ vec![
NodeId(12083027370457564588), NodeId(907133870432995942),
NodeId(10127202135369428481), NodeId(13049623730817360317),
NodeId(3781642984881236270), NodeId(2177355904460308500),
NodeId(12160249450476233602), NodeId(17479234042764485524),
NodeId(17962581471057044127), NodeId(10988236038173832469),
NodeId(7906594012485169109) NodeId(11097818235165626738),
] ]
); );
} }

View file

@ -1,8 +1,8 @@
use dyn_any::StaticType;
use graphene_core::application_io::SurfaceHandleFrame; use graphene_core::application_io::SurfaceHandleFrame;
use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId}; use graphene_core::application_io::{ApplicationError, ApplicationIo, ResourceFuture, SurfaceHandle, SurfaceId};
use wgpu_executor::WgpuExecutor; use wgpu_executor::WgpuExecutor;
use dyn_any::StaticType;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use js_sys::{Object, Reflect}; use js_sys::{Object, Reflect};
use std::collections::HashMap; use std::collections::HashMap;

View file

@ -1,13 +1,11 @@
use dyn_any::StaticType;
pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode}; pub use graph_craft::proto::{Any, NodeContainer, TypeErasedBox, TypeErasedNode};
use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer}; use graph_craft::proto::{DynFuture, FutureAny, SharedNodeContainer};
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
use graphene_core::NodeIO; use graphene_core::NodeIO;
use graphene_core::WasmNotSend; use graphene_core::WasmNotSend;
pub use graphene_core::{generic, ops, Node}; pub use graphene_core::{generic, ops, Node};
use dyn_any::StaticType;
pub use graphene_core::registry::{DowncastBothNode, DynAnyNode, FutureWrapperNode, PanicNode};
pub trait IntoTypeErasedNode<'n> { pub trait IntoTypeErasedNode<'n> {
fn into_type_erased(self) -> TypeErasedBox<'n>; fn into_type_erased(self) -> TypeErasedBox<'n>;
} }

View file

@ -90,8 +90,7 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64)
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
fn blit<P, BlendFn>(mut target: ImageFrameTable<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrameTable<P> fn blit<P, BlendFn>(mut target: ImageFrameTable<P>, texture: Image<P>, positions: Vec<DVec2>, blend_mode: BlendFn) -> ImageFrameTable<P>
where where
P: Pixel + Alpha + std::fmt::Debug + dyn_any::StaticType, P: Pixel + Alpha + std::fmt::Debug,
P::Static: Pixel,
BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>, BlendFn: for<'any_input> Node<'any_input, (P, P), Output = P>,
GraphicElement: From<Image<P>>, GraphicElement: From<Image<P>>,
{ {

View file

@ -14,8 +14,7 @@ use wgpu_executor::{Bindgroup, PipelineLayout, Shader, ShaderIO, ShaderInput, Wg
use glam::{DAffine2, DVec2, Mat2, Vec2}; use glam::{DAffine2, DVec2, Mat2, Vec2};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use std::sync::Mutex;
use crate::wasm_application_io::WasmApplicationIo; use crate::wasm_application_io::WasmApplicationIo;
@ -81,7 +80,7 @@ async fn map_gpu<'a: 'input>(image: ImageFrameTable<Color>, node: DocumentNode,
let name = "placeholder".to_string(); let name = "placeholder".to_string();
let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else { let Ok(compute_pass_descriptor) = create_compute_pass_descriptor(node, image_frame_table, executor).await else {
log::error!("Error creating compute pass descriptor in 'map_gpu()"); log::error!("Error creating compute pass descriptor in 'map_gpu()");
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
}; };
self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone()); self.cache.lock().as_mut().unwrap().insert(name, compute_pass_descriptor.clone());
log::error!("created compute pass"); log::error!("created compute pass");
@ -338,7 +337,7 @@ async fn blend_gpu_image(_: impl Ctx, foreground: ImageFrameTable<Color>, backgr
let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect(); let proto_networks: Result<Vec<_>, _> = compiler.compile(network.clone()).collect();
let Ok(proto_networks_result) = proto_networks else { let Ok(proto_networks_result) = proto_networks else {
log::error!("Error compiling network in 'blend_gpu_image()"); log::error!("Error compiling network in 'blend_gpu_image()");
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
}; };
let proto_networks = proto_networks_result; let proto_networks = proto_networks_result;
log::debug!("compiling shader"); log::debug!("compiling shader");

View file

@ -47,7 +47,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: ImageFra
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
} }
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type."); let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, data).expect("Failed to convert internal image format into image-rs data type.");
@ -257,8 +257,7 @@ fn mask_image<
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P> async fn blend_image_tuple<_P, MapFn, _Fg>(images: (ImageFrameTable<_P>, _Fg), map_fn: &'n MapFn) -> ImageFrameTable<_P>
where where
_P: Alpha + Pixel + Debug + Send + dyn_any::StaticType, _P: Alpha + Pixel + Debug + Send,
_P::Static: Pixel,
MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone, MapFn: for<'any_input> Node<'any_input, (_P, _P), Output = _P> + 'n + Clone,
_Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n, _Fg: Sample<Pixel = _P> + Transform + Clone + Send + 'n,
GraphicElement: From<Image<_P>>, GraphicElement: From<Image<_P>>,
@ -513,7 +512,7 @@ fn noise_pattern(
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
} }
let footprint_scale = footprint.scale(); let footprint_scale = footprint.scale();
@ -639,7 +638,7 @@ fn mandelbrot(ctx: impl ExtractFootprint + Send) -> ImageFrameTable<Color> {
// If the image would not be visible, return an empty image // If the image would not be visible, return an empty image
if size.x <= 0. || size.y <= 0. { if size.x <= 0. || size.y <= 0. {
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
} }
let scale = footprint.scale(); let scale = footprint.scale();

View file

@ -77,7 +77,7 @@ async fn load_resource<'a: 'n>(_: impl Ctx, _primary: (), #[scope("editor-api")]
#[node_macro::node(category("Network"))] #[node_macro::node(category("Network"))]
fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> { fn decode_image(_: impl Ctx, data: Arc<[u8]>) -> ImageFrameTable<Color> {
let Some(image) = image::load_from_memory(data.as_ref()).ok() else { let Some(image) = image::load_from_memory(data.as_ref()).ok() else {
return ImageFrameTable::empty(); return ImageFrameTable::one_empty_image();
}; };
let image = image.to_rgba32f(); let image = image.to_rgba32f();
let image = Image { let image = Image {
@ -216,7 +216,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
Context -> ImageFrameTable<Color>, Context -> ImageFrameTable<Color>,
Context -> GraphicGroupTable, Context -> GraphicGroupTable,
Context -> graphene_core::Artboard, Context -> graphene_core::Artboard,
Context -> graphene_core::ArtboardGroup, Context -> graphene_core::ArtboardGroupTable,
Context -> Option<Color>, Context -> Option<Color>,
Context -> Vec<Color>, Context -> Vec<Color>,
Context -> bool, Context -> bool,

View file

@ -114,7 +114,10 @@ impl DynamicExecutor {
} }
} }
impl<I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe> Executor<I, TaggedValue> for &DynamicExecutor { impl<I> Executor<I, TaggedValue> for &DynamicExecutor
where
I: StaticType + 'static + Send + Sync + std::panic::UnwindSafe,
{
fn execute(&self, input: I) -> LocalFuture<Result<TaggedValue, Box<dyn Error>>> { fn execute(&self, input: I) -> LocalFuture<Result<TaggedValue, Box<dyn Error>>> {
Box::pin(async move { Box::pin(async move {
use futures::FutureExt; use futures::FutureExt;
@ -225,14 +228,21 @@ impl BorrowTree {
} }
/// Evaluate the output node of the [`BorrowTree`]. /// Evaluate the output node of the [`BorrowTree`].
pub async fn eval<'i, I: StaticType + 'i + Send + Sync, O: StaticType + 'i>(&'i self, id: NodeId, input: I) -> Option<O> { pub async fn eval<'i, I, O>(&'i self, id: NodeId, input: I) -> Option<O>
where
I: StaticType + 'i + Send + Sync,
O: StaticType + 'i,
{
let (node, _path) = self.nodes.get(&id).cloned()?; let (node, _path) = self.nodes.get(&id).cloned()?;
let output = node.eval(Box::new(input)); let output = node.eval(Box::new(input));
dyn_any::downcast::<O>(output.await).ok().map(|o| *o) dyn_any::downcast::<O>(output.await).ok().map(|o| *o)
} }
/// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value. /// Evaluate the output node of the [`BorrowTree`] and cast it to a tagged value.
/// This ensures that no borrowed data can escape the node graph. /// This ensures that no borrowed data can escape the node graph.
pub async fn eval_tagged_value<I: StaticType + 'static + Send + Sync + UnwindSafe>(&self, id: NodeId, input: I) -> Result<TaggedValue, String> { pub async fn eval_tagged_value<I>(&self, id: NodeId, input: I) -> Result<TaggedValue, String>
where
I: StaticType + 'static + Send + Sync + UnwindSafe,
{
let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?; let (node, _path) = self.nodes.get(&id).cloned().ok_or("Output node not found in executor")?;
let output = node.eval(Box::new(input)); let output = node.eval(Box::new(input));
TaggedValue::try_from_any(output.await) TaggedValue::try_from_any(output.await)