Fix click targets (in, e.g., the boolean node) by resolving footprints from render output (#1946)

* add NodeId (u64) and Footprint to Graphic Group

* Render Output footprints

* Small bug fixes

* Commented out render output click targets/footprints

* Run graph when deleting

* Switch to node path

* Add upstream clicktargets for boolean operation

* Fix boolean operations

* Fix grouped layers

* Add click targets to vello render

* Add cache to artwork

* Fix demo artwork

* Improve recursion

* Code review

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-09-16 01:26:59 +00:00
committed by GitHub
co-authored by Dennis Kobert Keavon Chambers
parent ef007736f5
commit ca0d102296
28 changed files with 1003 additions and 670 deletions
Generated
+492 -407
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,6 +11,9 @@ use graphene_core::raster::BlendMode;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::Color; use graphene_core::Color;
use graphene_std::renderer::ClickTarget;
use graphene_std::transform::Footprint;
use graphene_std::vector::VectorData;
use glam::DAffine2; use glam::DAffine2;
@@ -155,6 +158,15 @@ pub enum DocumentMessage {
ToggleGridVisibility, ToggleGridVisibility,
ToggleOverlaysVisibility, ToggleOverlaysVisibility,
ToggleSnapping, ToggleSnapping,
UpdateUpstreamTransforms {
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
},
UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
},
UpdateVectorModify {
vector_modify: HashMap<NodeId, VectorData>,
},
Undo, Undo,
UngroupSelectedLayers, UngroupSelectedLayers,
UngroupLayer { UngroupLayer {
@@ -1022,8 +1022,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
if self.network_interface.transaction_status() == TransactionStatus::Finished { if self.network_interface.transaction_status() == TransactionStatus::Finished {
return; return;
} }
self.document_undo_history.pop_back();
self.network_interface.finish_transaction(); self.network_interface.finish_transaction();
self.undo(ipp, responses);
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
DocumentMessage::AddTransaction => { DocumentMessage::AddTransaction => {
@@ -1054,6 +1055,25 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled; self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
responses.add(PortfolioMessage::UpdateDocumentWidgets); responses.add(PortfolioMessage::UpdateDocumentWidgets);
} }
DocumentMessage::UpdateUpstreamTransforms { upstream_transforms } => {
self.network_interface.update_transforms(upstream_transforms);
}
DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets
let layer_click_targets = click_targets
.into_iter()
.filter_map(|(node_id, click_targets)| {
self.network_interface.is_layer(&node_id, &[]).then(|| {
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]);
(layer, click_targets)
})
})
.collect();
self.network_interface.update_click_targets(layer_click_targets);
}
DocumentMessage::UpdateVectorModify { vector_modify } => {
self.network_interface.update_vector_modify(vector_modify);
}
DocumentMessage::Undo => { DocumentMessage::Undo => {
if self.network_interface.transaction_status() != TransactionStatus::Finished { if self.network_interface.transaction_status() != TransactionStatus::Finished {
return; return;
@@ -1107,6 +1127,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
node_ids: vec![layer.to_node()], node_ids: vec![layer.to_node()],
delete_children: true, delete_children: true,
}); });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
} }
DocumentMessage::PTZUpdate => { DocumentMessage::PTZUpdate => {
if !self.graph_view_overlay_open { if !self.graph_view_overlay_open {
@@ -1239,7 +1262,7 @@ impl DocumentMessageHandler {
.filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface)) .filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface))
.filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface)) .filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface))
.filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[])) .filter(|&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
.filter_map(|layer| self.metadata().click_target(layer).map(|targets| (layer, targets))) .filter_map(|layer| self.metadata().click_targets(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| { .filter(move |(layer, target)| {
target target
.iter() .iter()
@@ -1256,7 +1279,7 @@ impl DocumentMessageHandler {
.all_layers() .all_layers()
.filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface)) .filter(|&layer| self.network_interface.selected_nodes(&[]).unwrap().layer_visible(layer, &self.network_interface))
.filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface)) .filter(|&layer| !self.network_interface.selected_nodes(&[]).unwrap().layer_locked(layer, &self.network_interface))
.filter_map(|layer| self.metadata().click_target(layer).map(|targets| (layer, targets))) .filter_map(|layer| self.metadata().click_targets(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(|target| target.intersect_point(point, self.metadata().transform_to_document(*layer)))) .filter(move |(layer, target)| target.iter().any(|target| target.intersect_point(point, self.metadata().transform_to_document(*layer))))
.map(|(layer, _)| layer) .map(|(layer, _)| layer)
} }
@@ -206,6 +206,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}); });
} }
// TODO: Replace deleted artboards with merge nodes // TODO: Replace deleted artboards with merge nodes
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
} }
GraphOperationMessage::NewSvg { GraphOperationMessage::NewSvg {
id, id,
@@ -253,8 +253,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Footprint)), manual_composition: Some(concrete!(Footprint)),
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)], inputs: vec![
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _>"), NodeInput::node(NodeId(1), 0),
NodeInput::node(NodeId(2), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _, _>"),
..Default::default() ..Default::default()
}, },
] ]
@@ -359,8 +363,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
inputs: vec![ inputs: vec![
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 0), NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 0),
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::AddArtboardNode<_, _>"), implementation: DocumentNodeImplementation::proto("graphene_core::AddArtboardNode<_, _, _>"),
..Default::default() ..Default::default()
}, },
] ]
@@ -3800,14 +3805,62 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Vector", category: "Vector",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorData), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode<_>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode<_, _, _>")),
manual_composition: Some(concrete!(Footprint)),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false), NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode<_>")),
..Default::default() ..Default::default()
}, },
persistent_node_metadata: DocumentNodePersistentMetadata { persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boolean Operation".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_names: vec!["Group of Paths".to_string(), "Operation".to_string()], input_names: vec!["Group of Paths".to_string(), "Operation".to_string()],
output_names: vec!["Vector".to_string()], output_names: vec!["Vector".to_string()],
..Default::default() ..Default::default()
@@ -114,7 +114,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SendSelectedNodes); responses.add(NodeGraphMessage::SendSelectedNodes);
responses.add(ArtboardToolMessage::UpdateSelectedArtboard); responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(OverlaysMessage::Draw);
responses.add(NodeGraphMessage::SendGraph);
} }
NodeGraphMessage::CreateWire { output_connector, input_connector } => { NodeGraphMessage::CreateWire { output_connector, input_connector } => {
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762 // TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762
@@ -203,8 +204,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
NodeGraphMessage::DeleteNodes { node_ids, delete_children } => { NodeGraphMessage::DeleteNodes { node_ids, delete_children } => {
network_interface.delete_nodes(node_ids, delete_children, selection_network_path); network_interface.delete_nodes(node_ids, delete_children, selection_network_path);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
} }
// Deletes selected_nodes. If `reconnect` is true, then all children nodes (secondary input) of the selected nodes are deleted and the siblings (primary input/output) are reconnected. // Deletes selected_nodes. If `reconnect` is true, then all children nodes (secondary input) of the selected nodes are deleted and the siblings (primary input/output) are reconnected.
// If `reconnect` is false, then only the selected nodes are deleted and not reconnected. // If `reconnect` is false, then only the selected nodes are deleted and not reconnected.
@@ -217,7 +216,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::DeleteNodes { responses.add(NodeGraphMessage::DeleteNodes {
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(), node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
delete_children, delete_children,
}) });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
} }
NodeGraphMessage::DisconnectInput { input_connector } => { NodeGraphMessage::DisconnectInput { input_connector } => {
network_interface.disconnect_input(&input_connector, selection_network_path); network_interface.disconnect_input(&input_connector, selection_network_path);
@@ -22,7 +22,6 @@ pub struct DocumentMetadata {
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>, pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub vector_modify: HashMap<NodeId, VectorData>, pub vector_modify: HashMap<NodeId, VectorData>,
// TODO: Remove and derive from document_ptz in document message handler
/// Transform from document space to viewport space. /// Transform from document space to viewport space.
pub document_to_viewport: DAffine2, pub document_to_viewport: DAffine2,
} }
@@ -52,7 +51,7 @@ impl DocumentMetadata {
self.structure.contains_key(&layer) self.structure.contains_key(&layer)
} }
pub fn click_target(&self, layer: LayerNodeIdentifier) -> Option<&Vec<ClickTarget>> { pub fn click_targets(&self, layer: LayerNodeIdentifier) -> Option<&Vec<ClickTarget>> {
self.click_targets.get(&layer) self.click_targets.get(&layer)
} }
@@ -71,29 +70,15 @@ impl DocumentMetadata {
// ============================ // ============================
impl DocumentMetadata { impl DocumentMetadata {
/// Update the cached transforms of the layers
pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
self.upstream_transforms = new_upstream_transforms;
}
/// Access the cached transformation to document space from layer space /// Access the cached transformation to document space from layer space
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
self.document_to_viewport.inverse() * self.transform_to_viewport(layer) self.document_to_viewport.inverse() * self.transform_to_viewport(layer)
} }
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
layer self.upstream_transforms
.ancestors(self) .get(&layer.to_node())
.filter_map(|ancestor_layer| { .map(|(footprint, transform)| footprint.transform * *transform)
if ancestor_layer != LayerNodeIdentifier::ROOT_PARENT {
self.upstream_transforms.get(&ancestor_layer.to_node())
} else {
None
}
})
.copied()
.map(|(footprint, transform)| footprint.transform * transform)
.next()
.unwrap_or(self.document_to_viewport) .unwrap_or(self.document_to_viewport)
} }
@@ -119,16 +104,9 @@ impl DocumentMetadata {
// =============================== // ===============================
impl DocumentMetadata { impl DocumentMetadata {
/// Update the cached click targets and vector modify values of the layers
pub fn update_from_monitor(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, new_vector_modify: HashMap<NodeId, VectorData>) {
self.click_targets = new_click_targets;
self.vector_modify = new_vector_modify;
}
/// Get the bounding box of the click target of the specified layer in the specified transform space /// Get the bounding box of the click target of the specified layer in the specified transform space
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> { pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
self.click_targets self.click_targets(layer)?
.get(&layer)?
.iter() .iter()
.filter_map(|click_target| click_target.subpath().bounding_box_with_transform(transform)) .filter_map(|click_target| click_target.subpath().bounding_box_with_transform(transform))
.reduce(Quad::combine_bounds) .reduce(Quad::combine_bounds)
@@ -11,6 +11,7 @@ use bezier_rs::Subpath;
use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::{concrete, Type}; use graph_craft::{concrete, Type};
use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::renderer::{ClickTarget, Quad};
use graphene_std::transform::Footprint;
use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY}; use interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY};
@@ -2327,7 +2328,7 @@ impl NodeNetworkInterface {
log::error!("Could not get nested node_metadata in position_from_downstream_node"); log::error!("Could not get nested node_metadata in position_from_downstream_node");
return None; return None;
}; };
match &node_metadata.persistent_metadata.node_type_metadata.clone() { match &node_metadata.persistent_metadata.node_type_metadata {
NodeTypePersistentMetadata::Layer(layer_metadata) => { NodeTypePersistentMetadata::Layer(layer_metadata) => {
match layer_metadata.position { match layer_metadata.position {
LayerPosition::Absolute(position) => Some(position), LayerPosition::Absolute(position) => Some(position),
@@ -2550,8 +2551,7 @@ impl NodeNetworkInterface {
} }
pub fn set_document_to_viewport_transform(&mut self, transform: DAffine2) { pub fn set_document_to_viewport_transform(&mut self, transform: DAffine2) {
let document_metadata = self.document_metadata_mut(); self.document_metadata.document_to_viewport = transform;
document_metadata.document_to_viewport = transform;
} }
pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
@@ -2882,7 +2882,7 @@ impl NodeNetworkInterface {
} }
for (parent, child) in children { for (parent, child) in children {
parent.push_child(self.document_metadata_mut(), child); parent.push_child(&mut self.document_metadata, child);
} }
while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() { while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {
@@ -2902,7 +2902,7 @@ impl NodeNetworkInterface {
} }
} }
for child in children { for child in children {
parent_layer_node.push_child(self.document_metadata_mut(), child); parent_layer_node.push_child(&mut self.document_metadata, child);
} }
} }
} }
@@ -2914,8 +2914,19 @@ impl NodeNetworkInterface {
self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer)); self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
} }
pub fn document_metadata_mut(&mut self) -> &mut DocumentMetadata { /// Update the cached transforms of the layers
&mut self.document_metadata pub fn update_transforms(&mut self, new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>) {
self.document_metadata.upstream_transforms = new_upstream_transforms;
}
/// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
self.document_metadata.click_targets = new_click_targets;
}
/// Update the vector modify of the layers
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, VectorData>) {
self.document_metadata.vector_modify = new_vector_modify;
} }
} }
@@ -3083,7 +3094,7 @@ impl NodeNetworkInterface {
} }
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn set_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) { pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) {
let Some(network) = self.network_mut(network_path) else { let Some(network) = self.network_mut(network_path) else {
log::error!("Could not get nested network in set_implementation"); log::error!("Could not get nested network in set_implementation");
return; return;
@@ -3095,6 +3106,20 @@ impl NodeNetworkInterface {
node.implementation = implementation; node.implementation = implementation;
} }
// TODO: Eventually remove this (probably starting late 2024)
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_implementation_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId], metadata: DocumentNodePersistentMetadata) {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
log::error!("Could not get network metdata in set implementation");
return;
};
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get_mut(node_id) else {
log::error!("Could not get persistent node metadata for node {node_id} in set implementation");
return;
};
node_metadata.persistent_metadata.network_metadata = metadata.network_metadata;
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_inputs(&mut self, node_id: &NodeId, inputs: Vec<NodeInput>, network_path: &[NodeId]) -> Vec<NodeInput> { pub fn replace_inputs(&mut self, node_id: &NodeId, inputs: Vec<NodeInput>, network_path: &[NodeId]) -> Vec<NodeInput> {
let Some(network) = self.network_mut(network_path) else { let Some(network) = self.network_mut(network_path) else {
@@ -425,7 +425,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
{ {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let default_definition_node = node_definition.default_node_template(); let default_definition_node = node_definition.default_node_template();
document.network_interface.set_implementation(node_id, &[], default_definition_node.document_node.implementation); document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
document
.network_interface
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
} }
} }
} }
@@ -461,7 +464,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
if reference == "Fill" && node.inputs.len() == 8 { if reference == "Fill" && node.inputs.len() == 8 {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let document_node = node_definition.default_node_template().document_node; let document_node = node_definition.default_node_template().document_node;
document.network_interface.set_implementation(node_id, &[], document_node.implementation.clone()); document.network_interface.replace_implementation(node_id, &[], document_node.implementation.clone());
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), &[]); let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), &[]);
@@ -515,6 +518,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
} }
} }
// Upgrade construct layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946
if reference == "Merge" || reference == "Artboard" {
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
let new_merge_node = node_definition.default_node_template();
document.network_interface.replace_implementation(node_id, &[], new_merge_node.document_node.implementation)
}
// Upgrade artboard name being passed as hidden value input to "To Artboard" // Upgrade artboard name being passed as hidden value input to "To Artboard"
if reference == "Artboard" { if reference == "Artboard" {
let label = document.network_interface.display_name(node_id, &[]); let label = document.network_interface.display_name(node_id, &[]);
@@ -391,6 +391,9 @@ impl SelectToolData {
}) })
.collect(); .collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original; self.layers_dragging = original;
} }
} }
@@ -413,12 +416,13 @@ impl Fsm for SelectToolFsmState {
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count; tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
tool_data.selected_layers_count = selected_layers_count; tool_data.selected_layers_count = selected_layers_count;
// Outline selected layers // Outline selected layers, but not artboards
for layer in document for layer in document
.network_interface .network_interface
.selected_nodes(&[]) .selected_nodes(&[])
.unwrap() .unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface) .selected_visible_and_unlocked_layers(&document.network_interface)
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
{ {
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer)); overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
} }
@@ -429,7 +433,7 @@ impl Fsm for SelectToolFsmState {
.selected_nodes(&[]) .selected_nodes(&[])
.unwrap() .unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface) .selected_visible_and_unlocked_layers(&document.network_interface)
.next() .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
.map(|layer| document.metadata().transform_to_viewport(layer)); .map(|layer| document.metadata().transform_to_viewport(layer));
let transform = transform.unwrap_or(DAffine2::IDENTITY); let transform = transform.unwrap_or(DAffine2::IDENTITY);
if transform.matrix2.determinant() == 0. { if transform.matrix2.determinant() == 0. {
@@ -440,6 +444,7 @@ impl Fsm for SelectToolFsmState {
.selected_nodes(&[]) .selected_nodes(&[])
.unwrap() .unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface) .selected_visible_and_unlocked_layers(&document.network_interface)
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
.filter_map(|layer| { .filter_map(|layer| {
document document
.metadata() .metadata()
@@ -649,7 +654,6 @@ impl Fsm for SelectToolFsmState {
} }
if let Some(intersection) = intersection { if let Some(intersection) = intersection {
tool_data.layer_selected_on_start = Some(intersection); tool_data.layer_selected_on_start = Some(intersection);
selected = intersection_list; selected = intersection_list;
@@ -694,7 +698,7 @@ impl Fsm for SelectToolFsmState {
let axis_align = input.keyboard.key(modifier_keys.axis_align); let axis_align = input.keyboard.key(modifier_keys.axis_align);
// Ignore the non duplicated layers if the current layers have not spawned yet. // Ignore the non duplicated layers if the current layers have not spawned yet.
let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_target(layer).is_some()); let layers_exist = tool_data.layers_dragging.iter().all(|&layer| document.metadata().click_targets(layer).is_some());
let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging); let ignore = tool_data.non_duplicated_layers.as_ref().filter(|_| !layers_exist).unwrap_or(&tool_data.layers_dragging);
let snap_data = SnapData::ignore(document, input, ignore); let snap_data = SnapData::ignore(document, input, ignore);
@@ -1195,9 +1199,12 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
} }
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) { fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
tool_data.layers_dragging.append(&mut vec![document tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(
.find_deepest(&selected) LayerNodeIdentifier::ROOT_PARENT
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next().expect("Child should exist when dragging deepest"))]); .children(document.metadata())
.next()
.expect("ROOT_PARENT should have a layer child when clicking"),
)]);
responses.add(NodeGraphMessage::SelectedNodesSet { responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data nodes: tool_data
.layers_dragging .layers_dragging
@@ -87,6 +87,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
*mouse_position = input.mouse.position; *mouse_position = input.mouse.position;
*start_mouse = input.mouse.position; *start_mouse = input.mouse.position;
selected.original_transforms.clear(); selected.original_transforms.clear();
selected.responses.add(DocumentMessage::StartTransaction);
}; };
match message { match message {
@@ -97,6 +99,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None; self.transform_operation = TransformOperation::None;
responses.add(DocumentMessage::EndTransaction);
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
@@ -156,6 +159,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
self.transform_operation = TransformOperation::None; self.transform_operation = TransformOperation::None;
responses.add(DocumentMessage::AbortTransaction);
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
TransformLayerMessage::ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap), TransformLayerMessage::ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
+47 -76
View File
@@ -1,26 +1,22 @@
use crate::consts::FILE_SAVE_SUFFIX; use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::node_graph::document_node_definitions::wrap_network_in_scope; use crate::messages::portfolio::document::node_graph::document_node_definitions::wrap_network_in_scope;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::{RenderOutput, TaggedValue};
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork}; use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler; use graph_craft::graphene_compiler::Compiler;
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::memo::IORecord; use graphene_core::memo::IORecord;
use graphene_core::raster::ImageFrame; use graphene_core::renderer::{GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_core::text::FontCache; use graphene_core::text::FontCache;
use graphene_core::transform::{Footprint, Transform}; use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::vector::VectorData; use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
use graphene_core::{Color, GraphicElement, SurfaceFrame};
use graphene_std::renderer::format_transform_matrix;
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta}; use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
@@ -48,12 +44,6 @@ pub struct NodeRuntime {
// TODO: Remove, it doesn't need to be persisted anymore // TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes. /// The current renders of the thumbnails for layer nodes.
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>, thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
/// The current click targets for layer nodes.
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
/// Vector data in Path nodes.
vector_modify: HashMap<NodeId, VectorData>,
/// The current upstream transforms for nodes.
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
} }
/// Messages passed from the editor thread to the node runtime thread. /// Messages passed from the editor thread to the node runtime thread.
@@ -83,9 +73,6 @@ pub struct ExecutionResponse {
execution_id: u64, execution_id: u64,
result: Result<TaggedValue, String>, result: Result<TaggedValue, String>,
responses: VecDeque<FrontendMessage>, responses: VecDeque<FrontendMessage>,
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_vector_modify: HashMap<NodeId, VectorData>,
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
transform: DAffine2, transform: DAffine2,
} }
@@ -144,9 +131,6 @@ impl NodeRuntime {
monitor_nodes: Vec::new(), monitor_nodes: Vec::new(),
thumbnail_renders: Default::default(), thumbnail_renders: Default::default(),
click_targets: HashMap::new(),
vector_modify: HashMap::new(),
upstream_transforms: HashMap::new(),
} }
} }
@@ -226,9 +210,6 @@ impl NodeRuntime {
execution_id, execution_id,
result, result,
responses, responses,
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_vector_modify: self.vector_modify.clone(),
new_upstream_transforms: self.upstream_transforms.clone(),
transform, transform,
}); });
} }
@@ -300,28 +281,9 @@ impl NodeRuntime {
}; };
if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() { if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses, update_thumbnails) Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::Artboard>>() { } else if let Some(io) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, parent_network_node_id, &io.output, responses, update_thumbnails) Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Footprint, VectorData>>() {
// Insert the vector modify if we are dealing with vector data
self.vector_modify.insert(parent_network_node_id, record.output.clone());
}
// If this is `VectorData`, `ImageFrame`, or `GraphicElement` data:
// Update the stored upstream transforms for this layer/node.
if let Some(transform) = {
fn try_downcast<T: Transform + 'static>(value: &dyn std::any::Any) -> Option<(Footprint, DAffine2)> {
let io_data = value.downcast_ref::<IORecord<Footprint, T>>()?;
let transform = io_data.output.transform();
Some((io_data.input, transform))
}
None.or_else(|| try_downcast::<VectorData>(introspected_data.as_ref()))
.or_else(|| try_downcast::<ImageFrame<Color>>(introspected_data.as_ref()))
.or_else(|| try_downcast::<GraphicElement>(introspected_data.as_ref()))
.or_else(|| try_downcast::<graphene_core::Artboard>(introspected_data.as_ref()))
} {
self.upstream_transforms.insert(parent_network_node_id, transform);
} }
} }
} }
@@ -330,16 +292,11 @@ impl NodeRuntime {
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI. // Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic_element( fn process_graphic_element(
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
click_targets: &mut HashMap<NodeId, Vec<ClickTarget>>,
parent_network_node_id: NodeId, parent_network_node_id: NodeId,
graphic_element: &impl GraphicElementRendered, graphic_element: &impl GraphicElementRendered,
responses: &mut VecDeque<FrontendMessage>, responses: &mut VecDeque<FrontendMessage>,
update_thumbnails: bool, update_thumbnails: bool,
) { ) {
let click_targets = click_targets.entry(parent_network_node_id).or_default();
click_targets.clear();
graphic_element.add_click_targets(click_targets);
// RENDER THUMBNAIL // RENDER THUMBNAIL
if !update_thumbnails { if !update_thumbnails {
@@ -536,8 +493,12 @@ impl NodeGraphExecutor {
} }
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> { fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
let TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) = node_graph_output else { let TaggedValue::RenderOutput(RenderOutput {
return Err("Incorrect render type for exportign (expected RenderOutput::Svg)".to_string()); data: graphene_std::wasm_application_io::RenderOutputType::Svg(svg),
..
}) = node_graph_output
else {
return Err("Incorrect render type for exporting (expected RenderOutput::Svg)".to_string());
}; };
let ExportConfig { let ExportConfig {
@@ -572,10 +533,7 @@ impl NodeGraphExecutor {
let ExecutionResponse { let ExecutionResponse {
execution_id, execution_id,
result, result,
new_click_targets,
responses: existing_responses, responses: existing_responses,
new_vector_modify,
new_upstream_transforms,
transform, transform,
} = execution_response; } = execution_response;
@@ -585,15 +543,13 @@ impl NodeGraphExecutor {
Ok(output) => output, Ok(output) => output,
Err(e) => { Err(e) => {
// Clear the click targets while the graph is in an un-renderable state // Clear the click targets while the graph is in an un-renderable state
document.network_interface.document_metadata_mut().update_from_monitor(HashMap::new(), HashMap::new()); document.network_interface.update_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new());
return Err(format!("Node graph evaluation failed:\n{e}")); return Err(format!("Node graph evaluation failed:\n{e}"));
} }
}; };
responses.extend(existing_responses.into_iter().map(Into::into)); responses.extend(existing_responses.into_iter().map(Into::into));
document.network_interface.document_metadata_mut().update_transforms(new_upstream_transforms);
document.network_interface.document_metadata_mut().update_from_monitor(new_click_targets, new_vector_modify);
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?; let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = execution_context.export_config { if let Some(export_config) = execution_context.export_config {
@@ -608,7 +564,10 @@ impl NodeGraphExecutor {
let type_delta = match result { let type_delta = match result {
Err(e) => { Err(e) => {
// Clear the click targets while the graph is in an un-renderable state // Clear the click targets while the graph is in an un-renderable state
document.network_interface.document_metadata_mut().update_from_monitor(HashMap::new(), HashMap::new());
document.network_interface.update_click_targets(HashMap::new());
document.network_interface.update_vector_modify(HashMap::new());
log::trace!("{e}"); log::trace!("{e}");
responses.add(NodeGraphMessage::UpdateTypes { responses.add(NodeGraphMessage::UpdateTypes {
@@ -654,25 +613,37 @@ impl NodeGraphExecutor {
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> { fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
match node_graph_output { match node_graph_output {
TaggedValue::SurfaceFrame(SurfaceFrame { .. }) => { TaggedValue::RenderOutput(render_output) => {
// TODO: Reimplement this now that document-legacy is gone let RenderMetadata {
} footprints,
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => { click_targets,
// Send to frontend vector_data,
responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); } = render_output.metadata;
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers); match render_output.data {
} graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => { // Send to frontend
let matrix = format_transform_matrix(frame.transform); responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) }; }
let svg = format!( graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>"#, let matrix = format_transform_matrix(frame.transform);
frame.resolution.x, frame.resolution.y, frame.surface_id.0 let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) };
); let svg = format!(
responses.add(FrontendMessage::UpdateDocumentArtwork { svg }); r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="canvas{}"></div></foreignObject></svg>"#,
frame.resolution.x, frame.resolution.y, frame.surface_id.0
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
_ => {
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
}
}
responses.add(DocumentMessage::UpdateUpstreamTransforms { upstream_transforms: footprints });
responses.add(DocumentMessage::UpdateClickTargets { click_targets });
responses.add(DocumentMessage::UpdateVectorModify { vector_modify: vector_data });
responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
} }
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses), TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses), TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
+2 -2
View File
@@ -499,8 +499,8 @@
// Dimming // Dimming
&.selected { &.selected {
// Halfway between 3-darkgray and 4-dimgray (this interpolation approach only works on grayscale values) // 1/3 between 3-darkgray and 4-dimgray (this interpolation approach only works on grayscale values)
--component: calc((Max(var(--color-3-darkgray-rgb)) + Max(var(--color-4-dimgray-rgb))) / 2); --component: calc((Max(var(--color-3-darkgray-rgb)) * 2 + Max(var(--color-4-dimgray-rgb))) / 3);
background: rgb(var(--component), var(--component), var(--component)); background: rgb(var(--component), var(--component), var(--component));
&.full-highlight { &.full-highlight {
+6 -4
View File
@@ -622,11 +622,13 @@ impl EditorHandle {
self.dispatch(message); self.dispatch(message);
let id = NodeId(id); let id = NodeId(id);
let message = NodeGraphMessage::DeleteNodes { self.dispatch(NodeGraphMessage::DeleteNodes {
node_ids: vec![id], node_ids: vec![id],
delete_children: true, delete_children: true,
}; });
self.dispatch(message); self.dispatch(NodeGraphMessage::RunDocumentGraph);
self.dispatch(NodeGraphMessage::SelectedNodesUpdated);
self.dispatch(NodeGraphMessage::SendGraph);
} }
/// Toggle lock state of a layer from the layer list /// Toggle lock state of a layer from the layer list
@@ -733,7 +735,7 @@ impl EditorHandle {
for node_id in nodes_to_upgrade { for node_id in nodes_to_upgrade {
document document
.network_interface .network_interface
.set_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>")); .replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
document document
.network_interface .network_interface
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string()); .add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());
+25 -12
View File
@@ -1,6 +1,7 @@
use crate::application_io::TextureFrame; use crate::application_io::TextureFrame;
use crate::raster::{BlendMode, ImageFrame}; use crate::raster::{BlendMode, ImageFrame};
use crate::transform::{Footprint, Transform, TransformMut}; use crate::transform::{Footprint, Transform, TransformMut};
use crate::uuid::NodeId;
use crate::vector::VectorData; use crate::vector::VectorData;
use crate::{Color, Node}; use crate::{Color, Node};
@@ -42,7 +43,7 @@ impl AlphaBlending {
#[derive(Clone, Debug, PartialEq, DynAny, Default)] #[derive(Clone, Debug, PartialEq, DynAny, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GraphicGroup { pub struct GraphicGroup {
elements: Vec<GraphicElement>, elements: Vec<(GraphicElement, Option<NodeId>)>,
pub transform: DAffine2, pub transform: DAffine2,
pub alpha_blending: AlphaBlending, pub alpha_blending: AlphaBlending,
} }
@@ -64,7 +65,7 @@ impl GraphicGroup {
pub fn new(elements: Vec<GraphicElement>) -> Self { pub fn new(elements: Vec<GraphicElement>) -> Self {
Self { Self {
elements, elements: elements.into_iter().map(|element| (element, None)).collect(),
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::new(), alpha_blending: AlphaBlending::new(),
} }
@@ -216,7 +217,7 @@ impl Artboard {
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)] #[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ArtboardGroup { pub struct ArtboardGroup {
pub artboards: Vec<Artboard>, pub artboards: Vec<(Artboard, Option<NodeId>)>,
} }
impl ArtboardGroup { impl ArtboardGroup {
@@ -226,14 +227,15 @@ impl ArtboardGroup {
Default::default() Default::default()
} }
fn add_artboard(&mut self, artboard: Artboard) { fn add_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
self.artboards.push(artboard); self.artboards.push((artboard, node_id));
} }
} }
pub struct ConstructLayerNode<Stack, GraphicElement> { pub struct ConstructLayerNode<Stack, GraphicElement, NodePath> {
stack: Stack, stack: Stack,
graphic_element: GraphicElement, graphic_element: GraphicElement,
node_path: NodePath,
} }
#[node_fn(ConstructLayerNode)] #[node_fn(ConstructLayerNode)]
@@ -241,6 +243,7 @@ async fn construct_layer<Data: Into<GraphicElement> + Send>(
footprint: crate::transform::Footprint, footprint: crate::transform::Footprint,
mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>, mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>,
graphic_element: impl Node<crate::transform::Footprint, Output = Data>, graphic_element: impl Node<crate::transform::Footprint, Output = Data>,
node_path: Vec<NodeId>,
) -> GraphicGroup { ) -> GraphicGroup {
let graphic_element = self.graphic_element.eval(footprint).await; let graphic_element = self.graphic_element.eval(footprint).await;
let mut stack = self.stack.eval(footprint).await; let mut stack = self.stack.eval(footprint).await;
@@ -252,7 +255,9 @@ async fn construct_layer<Data: Into<GraphicElement> + Send>(
stack.transform = DAffine2::IDENTITY; stack.transform = DAffine2::IDENTITY;
} }
stack.push(element); // 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 stack
} }
@@ -301,17 +306,25 @@ async fn construct_artboard(
clip, clip,
} }
} }
pub struct AddArtboardNode<ArtboardGroup, Artboard> { pub struct AddArtboardNode<ArtboardGroup, Artboard, NodePath> {
artboards: ArtboardGroup, artboards: ArtboardGroup,
artboard: Artboard, artboard: Artboard,
node_path: NodePath,
} }
#[node_fn(AddArtboardNode)] #[node_fn(AddArtboardNode)]
async fn add_artboard<Data: Into<Artboard> + Send>(footprint: Footprint, artboards: impl Node<Footprint, Output = ArtboardGroup>, artboard: impl Node<Footprint, Output = Data>) -> ArtboardGroup { async fn add_artboard<Data: Into<Artboard> + Send>(
footprint: Footprint,
artboards: impl Node<Footprint, Output = ArtboardGroup>,
artboard: impl Node<Footprint, Output = Data>,
node_path: Vec<NodeId>,
) -> ArtboardGroup {
let artboard = self.artboard.eval(footprint).await; let artboard = self.artboard.eval(footprint).await;
let mut artboards = self.artboards.eval(footprint).await; let mut artboards = self.artboards.eval(footprint).await;
artboards.add_artboard(artboard.into()); // 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.add_artboard(artboard.into(), encapsulating_node_id);
artboards artboards
} }
@@ -338,7 +351,7 @@ impl From<GraphicGroup> for GraphicElement {
} }
impl Deref for GraphicGroup { impl Deref for GraphicGroup {
type Target = Vec<GraphicElement>; type Target = Vec<(GraphicElement, Option<NodeId>)>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.elements &self.elements
} }
@@ -363,7 +376,7 @@ where
{ {
fn from(value: T) -> Self { fn from(value: T) -> Self {
Self { Self {
elements: (vec![value.into()]), elements: (vec![(value.into(), None)]),
transform: DAffine2::IDENTITY, transform: DAffine2::IDENTITY,
alpha_blending: AlphaBlending::default(), alpha_blending: AlphaBlending::default(),
} }
+143 -50
View File
@@ -4,24 +4,26 @@ pub use quad::Quad;
pub use rect::Rect; pub use rect::Rect;
use crate::raster::{BlendMode, Image, ImageFrame}; use crate::raster::{BlendMode, Image, ImageFrame};
use crate::transform::Transform; use crate::transform::{Footprint, Transform};
use crate::uuid::generate_uuid; use crate::uuid::{generate_uuid, NodeId};
use crate::vector::style::{Fill, Stroke, ViewMode}; use crate::vector::style::{Fill, Stroke, ViewMode};
use crate::vector::PointId; use crate::vector::PointId;
use crate::Raster; use crate::Raster;
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup}; use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
use bezier_rs::Subpath; use bezier_rs::Subpath;
use dyn_any::{DynAny, StaticType};
use base64::Engine; use base64::Engine;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use num_traits::Zero; use num_traits::Zero;
use std::collections::HashMap;
use std::fmt::Write; use std::fmt::Write;
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
use vello::*; use vello::*;
/// Represents a clickable target for the layer /// Represents a clickable target for the layer
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ClickTarget { pub struct ClickTarget {
subpath: bezier_rs::Subpath<PointId>, subpath: bezier_rs::Subpath<PointId>,
stroke_width: f64, stroke_width: f64,
@@ -268,16 +270,35 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32) usvg::Transform::from_row(cols[0] as f32, cols[1] as f32, cols[2] as f32, cols[3] as f32, cols[4] as f32, cols[5] as f32)
} }
#[derive(Debug, Clone, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderMetadata {
pub footprints: HashMap<NodeId, (Footprint, DAffine2)>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub vector_data: HashMap<NodeId, VectorData>,
}
pub trait GraphicElementRendered { pub trait GraphicElementRendered {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams); fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>; fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]>;
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>);
// The upstream click targets for each layer are collected during the render so that they do not have to be calculated for each click detection
fn add_upstream_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
// TODO: Store all click targets in a vec which contains the AABB, click target, and path
// fn add_click_targets(&self, click_targets: &mut Vec<([DVec2; 2], ClickTarget, Vec<NodeId>)>, current_path: Option<NodeId>) {}
// Recursively iterate over data in the render (including groups upstream from vector data in the case of a boolean operation) to collect the footprints, click targets, and vector modify
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene { fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene {
let mut scene = vello::Scene::new(); let mut scene = vello::Scene::new();
self.render_to_vello(&mut scene, transform, context); self.render_to_vello(&mut scene, transform, context);
scene scene
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {} fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {}
@@ -305,7 +326,7 @@ impl GraphicElementRendered for GraphicGroup {
} }
}, },
|render| { |render| {
for element in self.iter() { for (element, _) in self.iter() {
element.render_svg(render, render_params); element.render_svg(render, render_params);
} }
}, },
@@ -313,16 +334,35 @@ impl GraphicElementRendered for GraphicGroup {
} }
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
self.iter().filter_map(|element| element.bounding_box(transform * self.transform)).reduce(Quad::combine_bounds) self.iter().filter_map(|(element, _)| element.bounding_box(transform * self.transform)).reduce(Quad::combine_bounds)
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
for element in self.elements.iter() { footprint.transform *= self.transform;
for (element, element_id) in self.elements.iter() {
if let Some(element_id) = element_id {
element.collect_metadata(metadata, footprint, Some(*element_id));
}
}
if let Some(graphic_group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
self.add_upstream_click_targets(&mut all_upstream_click_targets);
metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for (element, _) in self.elements.iter() {
let mut new_click_targets = Vec::new(); let mut new_click_targets = Vec::new();
element.add_click_targets(&mut new_click_targets);
element.add_upstream_click_targets(&mut new_click_targets);
for click_target in new_click_targets.iter_mut() { for click_target in new_click_targets.iter_mut() {
click_target.apply_transform(element.transform()) click_target.apply_transform(element.transform())
} }
click_targets.extend(new_click_targets); click_targets.extend(new_click_targets);
} }
} }
@@ -344,16 +384,18 @@ impl GraphicElementRendered for GraphicGroup {
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y), &vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
); );
} }
for element in self.iter() {
for (element, _) in self.iter() {
element.render_to_vello(scene, child_transform, context); element.render_to_vello(scene, child_transform, context);
} }
if layer { if layer {
scene.pop_layer(); scene.pop_layer();
} }
} }
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
self.iter().any(|element| element.contains_artboard()) self.iter().any(|(element, _)| element.contains_artboard())
} }
} }
@@ -402,7 +444,29 @@ impl GraphicElementRendered for VectorData {
self.bounding_box_with_transform(transform * self.transform).map(|[a, b]| [a - offset, b + offset]) self.bounding_box_with_transform(transform * self.transform).map(|[a, b]| [a - offset, b + offset])
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id {
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = self.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
}
subpath
};
metadata
.click_targets
.insert(element_id, self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)).collect());
metadata.vector_data.insert(element_id, self.clone());
}
if let Some(upstream_graphic_group) = &self.upstream_graphic_group {
footprint.transform *= self.transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight); let stroke_width = self.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = self.style.fill() != &Fill::None; let filled = self.style.fill() != &Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| { let fill = |mut subpath: bezier_rs::Subpath<_>| {
@@ -558,21 +622,13 @@ impl GraphicElementRendered for Artboard {
"g", "g",
// Group tag attributes // Group tag attributes
|attributes| { |attributes| {
let matrix = format_transform_matrix(DAffine2::from_translation(self.location.as_dvec2()) * self.graphic_group.transform);
if !matrix.is_empty() {
attributes.push("transform", matrix);
}
if self.clip { if self.clip {
let id = format!("artboard-{}", generate_uuid()); let id = format!("artboard-{}", generate_uuid());
let selector = format!("url(#{id})"); let selector = format!("url(#{id})");
let matrix = format_transform_matrix(self.graphic_group.transform.inverse());
let transform = if matrix.is_empty() { String::new() } else { format!(r#" transform="{matrix}""#) };
write!( write!(
&mut attributes.0.svg_defs, &mut attributes.0.svg_defs,
r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}"{transform} /></clipPath>"##, r##"<clipPath id="{id}"><rect x="0" y="0" width="{}" height="{}" /></clipPath>"##,
self.dimensions.x, self.dimensions.y self.dimensions.x, self.dimensions.y
) )
.unwrap(); .unwrap();
@@ -581,9 +637,7 @@ impl GraphicElementRendered for Artboard {
}, },
// Artboard contents // Artboard contents
|render| { |render| {
for element in self.graphic_group.iter() { self.graphic_group.render_svg(render, render_params);
element.render_svg(render, render_params);
}
}, },
); );
} }
@@ -597,6 +651,25 @@ impl GraphicElementRendered for Artboard {
} }
} }
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id {
let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, DAffine2::from_translation(self.location.as_dvec2())));
}
self.graphic_group.collect_metadata(metadata, footprint, None);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
if self.graphic_group.transform.matrix2.determinant() != 0. {
subpath.apply_transform(self.graphic_group.transform.inverse());
click_targets.push(ClickTarget::new(subpath, 0.));
}
}
#[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) {
use vello::peniko; use vello::peniko;
@@ -621,14 +694,6 @@ impl GraphicElementRendered for Artboard {
} }
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let mut subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
if self.graphic_group.transform.matrix2.determinant() != 0. {
subpath.apply_transform(self.graphic_group.transform.inverse());
click_targets.push(ClickTarget::new(subpath, 0.));
}
}
fn contains_artboard(&self) -> bool { fn contains_artboard(&self) -> bool {
true true
} }
@@ -636,24 +701,30 @@ impl GraphicElementRendered for Artboard {
impl GraphicElementRendered for crate::ArtboardGroup { impl GraphicElementRendered for crate::ArtboardGroup {
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.artboards {
artboard.render_svg(render, render_params); artboard.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.artboards.iter().filter_map(|(element, _)| element.bounding_box(transform)).reduce(Quad::combine_bounds)
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for artboard in &self.artboards { for (artboard, element_id) in &self.artboards {
artboard.add_click_targets(click_targets); artboard.collect_metadata(metadata, footprint, *element_id);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for (artboard, _) in &self.artboards {
artboard.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 (artboard, _) in &self.artboards {
artboard.render_to_vello(scene, transform, context) artboard.render_to_vello(scene, transform, context)
} }
} }
@@ -704,7 +775,15 @@ impl GraphicElementRendered for ImageFrame<Color> {
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) (transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, self.transform));
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
click_targets.push(ClickTarget::new(subpath, 0.)); click_targets.push(ClickTarget::new(subpath, 0.));
} }
@@ -774,7 +853,15 @@ impl GraphicElementRendered for Raster {
(transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box()) (transform.matrix2 != glam::DMat2::ZERO).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
let Some(element_id) = element_id else { return };
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
metadata.footprints.insert(element_id, (footprint, self.transform()));
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE); let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
click_targets.push(ClickTarget::new(subpath, 0.)); click_targets.push(ClickTarget::new(subpath, 0.));
} }
@@ -836,11 +923,23 @@ impl GraphicElementRendered for GraphicElement {
} }
} }
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id {
metadata.footprints.insert(element_id, (footprint, self.transform()));
}
match self { match self {
GraphicElement::VectorData(vector_data) => vector_data.add_click_targets(click_targets), GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
GraphicElement::Raster(raster) => raster.add_click_targets(click_targets), GraphicElement::Raster(raster) => raster.collect_metadata(metadata, footprint, element_id),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets), GraphicElement::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
GraphicElement::Raster(raster) => raster.add_upstream_click_targets(click_targets),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
} }
} }
@@ -884,8 +983,6 @@ impl<T: Primitive> GraphicElementRendered for T {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None None
} }
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
} }
impl GraphicElementRendered for Option<Color> { impl GraphicElementRendered for Option<Color> {
@@ -911,8 +1008,6 @@ impl GraphicElementRendered for Option<Color> {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None None
} }
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
} }
impl GraphicElementRendered for Vec<Color> { impl GraphicElementRendered for Vec<Color> {
@@ -934,8 +1029,6 @@ impl GraphicElementRendered for Vec<Color> {
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> { fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
None None
} }
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
} }
/// A segment of an svg string to allow for embedding blob urls /// A segment of an svg string to allow for embedding blob urls
+1 -1
View File
@@ -103,7 +103,7 @@ impl TransformMut for VectorData {
impl Transform for Artboard { impl Transform for Artboard {
fn transform(&self) -> DAffine2 { fn transform(&self) -> DAffine2 {
DAffine2::from_translation(self.location.as_dvec2()) * self.graphic_group.transform DAffine2::from_translation(self.location.as_dvec2())
} }
fn local_pivot(&self, pivot: DVec2) -> DVec2 { fn local_pivot(&self, pivot: DVec2) -> DVec2 {
self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot
+21 -1
View File
@@ -1,3 +1,8 @@
pub use uuid_generation::*;
use dyn_any::DynAny;
use dyn_any::StaticType;
#[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")]
@@ -66,4 +71,19 @@ mod uuid_generation {
} }
} }
pub use uuid_generation::*; #[repr(transparent)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type, DynAny)]
pub struct NodeId(pub u64);
// TODO: Find and replace all `NodeId(generate_uuid())` with `NodeId::new()`.
impl NodeId {
pub fn new() -> Self {
Self(generate_uuid())
}
}
impl core::fmt::Display for NodeId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
+6
View File
@@ -742,6 +742,12 @@ impl PathStyle {
self.fill = fill; self.fill = fill;
} }
pub fn set_stroke_transform(&mut self, transform: DAffine2) {
if let Some(stroke) = &mut self.stroke {
stroke.transform = transform;
}
}
/// Replace the path's [Stroke] with a provided one. /// Replace the path's [Stroke] with a provided one.
/// ///
/// # Example /// # Example
@@ -27,6 +27,9 @@ pub struct VectorData {
pub point_domain: PointDomain, pub point_domain: PointDomain,
pub segment_domain: SegmentDomain, pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain, pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<crate::GraphicGroup>,
} }
impl core::hash::Hash for VectorData { impl core::hash::Hash for VectorData {
@@ -52,6 +55,7 @@ impl VectorData {
point_domain: PointDomain::new(), point_domain: PointDomain::new(),
segment_domain: SegmentDomain::new(), segment_domain: SegmentDomain::new(),
region_domain: RegionDomain::new(), region_domain: RegionDomain::new(),
upstream_graphic_group: None,
} }
} }
+3 -3
View File
@@ -23,7 +23,7 @@ pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, Seed, Re
#[node_macro::node_fn(AssignColorsNode)] #[node_macro::node_fn(AssignColorsNode)]
fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup { fn assign_colors_node(group: GraphicGroup, fill: bool, stroke: bool, gradient: GradientStops, reverse: bool, randomize: bool, seed: u32, repeat_every: u32) -> GraphicGroup {
let mut group = group; let mut group = group;
let vector_data_list: Vec<_> = group.iter_mut().filter_map(|element| element.as_vector_data_mut()).collect(); let vector_data_list: Vec<_> = group.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).collect();
let list = (vector_data_list.len(), vector_data_list.into_iter()); let list = (vector_data_list.len(), vector_data_list.into_iter());
assign_colors( assign_colors(
@@ -298,9 +298,9 @@ pub trait ConcatElement {
impl ConcatElement for GraphicGroup { impl ConcatElement for GraphicGroup {
fn concat(&mut self, other: &Self, transform: DAffine2) { fn concat(&mut self, other: &Self, transform: DAffine2) {
// TODO: Decide if we want to keep this behavior whereby the layers are flattened // TODO: Decide if we want to keep this behavior whereby the layers are flattened
for mut element in other.iter().cloned() { for (mut element, footprint_mapping) in other.iter().cloned() {
*element.transform_mut() = transform * element.transform() * other.transform(); *element.transform_mut() = transform * element.transform() * other.transform();
self.push(element); self.push((element, footprint_mapping));
} }
self.alpha_blending = other.alpha_blending; self.alpha_blending = other.alpha_blending;
} }
+2 -19
View File
@@ -4,7 +4,9 @@ use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use graphene_core::memo::MemoHashGuard; use graphene_core::memo::MemoHashGuard;
pub use graphene_core::uuid::generate_uuid; pub use graphene_core::uuid::generate_uuid;
pub use graphene_core::uuid::NodeId;
use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type}; use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type};
pub mod value;
use glam::IVec2; use glam::IVec2;
use log::Metadata; use log::Metadata;
@@ -13,25 +15,6 @@ use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
pub mod value;
#[repr(transparent)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type, DynAny)]
pub struct NodeId(pub u64);
// TODO: Find and replace all `NodeId(generate_uuid())` with `NodeId::new()`.
impl NodeId {
pub fn new() -> Self {
Self(generate_uuid())
}
}
impl core::fmt::Display for NodeId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.0)
}
}
/// Hash two IDs together, returning a new ID that is always consistent for two input IDs in a specific order. /// Hash two IDs together, returning a new ID that is always consistent for two input IDs in a specific order.
/// This is used during [`NodeNetwork::flatten`] in order to ensure consistent yet non-conflicting IDs for inner networks. /// This is used during [`NodeNetwork::flatten`] in order to ensure consistent yet non-conflicting IDs for inner networks.
fn merge_ids(a: NodeId, b: NodeId) -> NodeId { fn merge_ids(a: NodeId, b: NodeId) -> NodeId {
+20 -6
View File
@@ -1,15 +1,16 @@
use super::DocumentNode; use super::DocumentNode;
use crate::document::NodeId;
pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use crate::proto::{Any as DAny, FutureAny}; use crate::proto::{Any as DAny, FutureAny};
use crate::wasm_application_io::WasmEditorApi; use crate::wasm_application_io::WasmEditorApi;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{BlendMode, LuminanceCalculation};
use graphene_core::{Color, MemoHash, Node, Type};
use dyn_any::DynAny; use dyn_any::DynAny;
pub use dyn_any::StaticType; pub use dyn_any::StaticType;
use graphene_core::raster::brush_cache::BrushCache;
use graphene_core::raster::{BlendMode, LuminanceCalculation};
use graphene_core::renderer::RenderMetadata;
use graphene_core::uuid::NodeId;
use graphene_core::{Color, MemoHash, Node, Type};
pub use glam::{DAffine2, DVec2, IVec2, UVec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use std::fmt::Display; use std::fmt::Display;
use std::hash::Hash; use std::hash::Hash;
@@ -248,14 +249,27 @@ impl<T: AsRef<U> + Sync + Send, U: Sync + Send> UpcastAsRefNode<T, U> {
} }
} }
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderOutput {
pub data: RenderOutputType,
pub metadata: RenderMetadata,
}
#[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)] #[derive(Debug, Clone, PartialEq, dyn_any::DynAny, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RenderOutput { pub enum RenderOutputType {
CanvasFrame(graphene_core::SurfaceFrame), CanvasFrame(graphene_core::SurfaceFrame),
Svg(String), Svg(String),
Image(Vec<u8>), Image(Vec<u8>),
} }
impl Hash for RenderOutput {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state)
}
}
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack) /// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
trait FakeHash { trait FakeHash {
fn hash<H: core::hash::Hasher>(&self, state: &mut H); fn hash<H: core::hash::Hasher>(&self, state: &mut H);
+10 -2
View File
@@ -42,7 +42,7 @@ fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperat
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> { fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> {
// Ensure all non vector data in the graphic group is converted to vector data // Ensure all non vector data in the graphic group is converted to vector data
let vector_data = graphic_group.iter().map(union_vector_data); let vector_data = graphic_group.iter().map(|(element, _)| union_vector_data(element));
// Apply the transform from the parent graphic group // Apply the transform from the parent graphic group
let transformed_vector_data = vector_data.map(|mut vector_data| { let transformed_vector_data = vector_data.map(|mut vector_data| {
vector_data.transform = graphic_group.transform * vector_data.transform; vector_data.transform = graphic_group.transform * vector_data.transform;
@@ -174,7 +174,15 @@ fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperat
} }
// The first index is the bottom of the stack // The first index is the bottom of the stack
boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation) let mut boolean_operation_result = boolean_operation_on_vector_data(&collect_vector_data(&group_of_paths), operation);
let transform = boolean_operation_result.transform;
VectorData::transform(&mut boolean_operation_result, transform);
boolean_operation_result.style.set_stroke_transform(DAffine2::IDENTITY);
boolean_operation_result.transform = DAffine2::IDENTITY;
boolean_operation_result.upstream_graphic_group = Some(group_of_paths);
boolean_operation_result
} }
fn to_svg_string(vector: &VectorData, transform: DAffine2) -> String { fn to_svg_string(vector: &VectorData, transform: DAffine2) -> String {
+42 -27
View File
@@ -1,4 +1,5 @@
pub use graph_craft::document::value::RenderOutput; use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType;
pub use graph_craft::wasm_application_io::*; pub use graph_craft::wasm_application_io::*;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use graphene_core::application_io::SurfaceHandle; use graphene_core::application_io::SurfaceHandle;
@@ -7,6 +8,7 @@ use graphene_core::application_io::{ApplicationIo, ExportFormat, RenderConfig};
use graphene_core::raster::bbox::Bbox; use graphene_core::raster::bbox::Bbox;
use graphene_core::raster::Image; use graphene_core::raster::Image;
use graphene_core::raster::ImageFrame; use graphene_core::raster::ImageFrame;
use graphene_core::renderer::RenderMetadata;
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender}; use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, RenderSvgSegmentList, SvgRender};
use graphene_core::transform::Footprint; use graphene_core::transform::Footprint;
use graphene_core::Node; use graphene_core::Node;
@@ -16,6 +18,7 @@ use graphene_core::{Color, WasmNotSend};
use base64::Engine; use base64::Engine;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use glam::DAffine2; use glam::DAffine2;
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::Clamped; use wasm_bindgen::Clamped;
@@ -86,7 +89,7 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
image image
} }
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput { fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutputType {
if !data.contains_artboard() && !render_params.hide_artboards { if !data.contains_artboard() && !render_params.hide_artboards {
render.leaf_tag("rect", |attributes| { render.leaf_tag("rect", |attributes| {
attributes.push("x", "0"); attributes.push("x", "0");
@@ -102,42 +105,43 @@ fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_p
} }
data.render_svg(&mut render, &render_params); data.render_svg(&mut render, &render_params);
render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2())); render.wrap_with_transform(footprint.transform, Some(footprint.resolution.as_dvec2()));
RenderOutput::Svg(render.svg.to_svg_string()) RenderOutputType::Svg(render.svg.to_svg_string())
} }
#[cfg(feature = "vello")] #[cfg(feature = "vello")]
#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutput { async fn render_canvas(render_config: RenderConfig, data: impl GraphicElementRendered, editor: &WasmEditorApi, surface_handle: wgpu_executor::WgpuSurface) -> RenderOutputType {
use graphene_core::SurfaceFrame; use graphene_core::SurfaceFrame;
if let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() { let footprint = render_config.viewport;
use vello::*; let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else {
let footprint = render_config.viewport;
let mut scene = Scene::new();
let mut child = Scene::new();
let mut context = wgpu_executor::RenderContext::default();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context)
.await
.expect("Failed to render Vello scene");
} else {
unreachable!("Attempted to render with Vello when no GPU executor is available"); unreachable!("Attempted to render with Vello when no GPU executor is available");
} };
use vello::*;
let mut scene = Scene::new();
let mut child = Scene::new();
let mut context = wgpu_executor::RenderContext::default();
data.render_to_vello(&mut child, glam::DAffine2::IDENTITY, &mut context);
// TODO: Instead of applying the transform here, pass the transform during the translation to avoid the O(Nr cost
scene.append(&child, Some(kurbo::Affine::new(footprint.transform.to_cols_array())));
exec.render_vello_scene(&scene, &surface_handle, footprint.resolution.x, footprint.resolution.y, &context)
.await
.expect("Failed to render Vello scene");
let frame = SurfaceFrame { let frame = SurfaceFrame {
surface_id: surface_handle.window_id, surface_id: surface_handle.window_id,
resolution: render_config.viewport.resolution, resolution: render_config.viewport.resolution,
transform: glam::DAffine2::IDENTITY, transform: glam::DAffine2::IDENTITY,
}; };
RenderOutput::CanvasFrame(frame)
RenderOutputType::CanvasFrame(frame)
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@@ -225,13 +229,23 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
#[cfg(all(feature = "vello", target_arch = "wasm32"))] #[cfg(all(feature = "vello", target_arch = "wasm32"))]
let use_vello = use_vello && surface_handle.is_some(); let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata {
footprints: HashMap::new(),
click_targets: HashMap::new(),
vector_data: HashMap::new(),
};
data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format; let output_format = render_config.export_format;
match output_format { let data = match output_format {
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint), ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
ExportFormat::Canvas => { ExportFormat::Canvas => {
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() { if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
#[cfg(all(feature = "vello", target_arch = "wasm32"))] #[cfg(all(feature = "vello", target_arch = "wasm32"))]
return render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await; return RenderOutput {
data: render_canvas(render_config, data, editor_api, surface_handle.unwrap()).await,
metadata,
};
#[cfg(not(all(feature = "vello", target_arch = "wasm32")))] #[cfg(not(all(feature = "vello", target_arch = "wasm32")))]
render_svg(data, SvgRender::new(), render_params, footprint) render_svg(data, SvgRender::new(), render_params, footprint)
} else { } else {
@@ -239,5 +253,6 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
} }
} }
_ => todo!("Non-SVG render output for {output_format:?}"), _ => todo!("Non-SVG render output for {output_format:?}"),
} };
RenderOutput { data, metadata }
} }
@@ -1,3 +1,5 @@
use dyn_any::StaticType;
use graph_craft::document::value::RenderOutput;
use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod}; use graph_craft::imaginate_input::{ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
use graph_craft::proto::{NodeConstructor, TypeErasedBox}; use graph_craft::proto::{NodeConstructor, TypeErasedBox};
use graphene_core::fn_type; use graphene_core::fn_type;
@@ -19,6 +21,7 @@ use graphene_core::{Node, NodeIO, NodeIOTypes};
use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode}; use graphene_std::any::{ComposeTypeErased, DowncastBothNode, DynAnyNode, FutureWrapperNode, IntoTypeErasedNode};
use graphene_std::application_io::{RenderConfig, TextureFrame}; use graphene_std::application_io::{RenderConfig, TextureFrame};
use graphene_std::raster::*; use graphene_std::raster::*;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::GradientStops; use graphene_std::vector::style::GradientStops;
use graphene_std::wasm_application_io::*; use graphene_std::wasm_application_io::*;
use graphene_std::GraphicElement; use graphene_std::GraphicElement;
@@ -26,7 +29,6 @@ use graphene_std::GraphicElement;
use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput}; use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput};
use wgpu_executor::{WgpuSurface, WindowHandle}; use wgpu_executor::{WgpuSurface, WindowHandle};
use dyn_any::StaticType;
use glam::{DAffine2, DVec2, UVec2}; use glam::{DAffine2, DVec2, UVec2};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
@@ -794,7 +796,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]), register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]),
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: &WasmEditorApi, params: [String, graphene_core::text::Font, f64]), register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: &WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
async_node!(graphene_core::ConstructLayerNode<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, Footprint => graphene_core::GraphicElement]), async_node!(graphene_core::ConstructLayerNode<_, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, Footprint => graphene_core::GraphicElement, () => Vec<NodeId>]),
register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []), register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []), register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, params: []), register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, params: []),
@@ -803,7 +805,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::ToGraphicGroupNode, input: ImageFrame<Color>, params: []), register_node!(graphene_core::ToGraphicGroupNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []), register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []),
async_node!(graphene_core::ConstructArtboardNode<_, _, _, _, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => GraphicGroup, () => String, () => glam::IVec2, () => glam::IVec2, () => Color, () => bool]), async_node!(graphene_core::ConstructArtboardNode<_, _, _, _, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => GraphicGroup, () => String, () => glam::IVec2, () => glam::IVec2, () => Color, () => bool]),
async_node!(graphene_core::AddArtboardNode<_, _>, input: Footprint, output: ArtboardGroup, fn_params: [Footprint => ArtboardGroup, Footprint => Artboard]), async_node!(graphene_core::AddArtboardNode<_, _, _>, input: Footprint, output: ArtboardGroup, fn_params: [Footprint => ArtboardGroup, Footprint => Artboard, () => Vec<NodeId>]),
]; ];
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new(); let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() { for (id, c, types) in node_types.into_iter().flatten() {