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:
parent
ef007736f5
commit
ca0d102296
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::vector::style::ViewMode;
|
||||
use graphene_core::Color;
|
||||
use graphene_std::renderer::ClickTarget;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::VectorData;
|
||||
|
||||
use glam::DAffine2;
|
||||
|
||||
|
|
@ -155,6 +158,15 @@ pub enum DocumentMessage {
|
|||
ToggleGridVisibility,
|
||||
ToggleOverlaysVisibility,
|
||||
ToggleSnapping,
|
||||
UpdateUpstreamTransforms {
|
||||
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
},
|
||||
UpdateClickTargets {
|
||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
},
|
||||
UpdateVectorModify {
|
||||
vector_modify: HashMap<NodeId, VectorData>,
|
||||
},
|
||||
Undo,
|
||||
UngroupSelectedLayers,
|
||||
UngroupLayer {
|
||||
|
|
|
|||
|
|
@ -1022,8 +1022,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
if self.network_interface.transaction_status() == TransactionStatus::Finished {
|
||||
return;
|
||||
}
|
||||
|
||||
self.document_undo_history.pop_back();
|
||||
self.network_interface.finish_transaction();
|
||||
self.undo(ipp, responses);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::AddTransaction => {
|
||||
|
|
@ -1054,6 +1055,25 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
self.snapping_state.snapping_enabled = !self.snapping_state.snapping_enabled;
|
||||
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 => {
|
||||
if self.network_interface.transaction_status() != TransactionStatus::Finished {
|
||||
return;
|
||||
|
|
@ -1107,6 +1127,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
node_ids: vec![layer.to_node()],
|
||||
delete_children: true,
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::PTZUpdate => {
|
||||
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_locked(layer, &self.network_interface))
|
||||
.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)| {
|
||||
target
|
||||
.iter()
|
||||
|
|
@ -1256,7 +1279,7 @@ impl DocumentMessageHandler {
|
|||
.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_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))))
|
||||
.map(|(layer, _)| layer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||
});
|
||||
}
|
||||
// TODO: Replace deleted artboards with merge nodes
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
GraphOperationMessage::NewSvg {
|
||||
id,
|
||||
|
|
|
|||
|
|
@ -253,8 +253,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
DocumentNode {
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructLayerNode<_, _>"),
|
||||
inputs: vec![
|
||||
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()
|
||||
},
|
||||
]
|
||||
|
|
@ -359,8 +363,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
inputs: vec![
|
||||
NodeInput::network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup))), 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()
|
||||
},
|
||||
]
|
||||
|
|
@ -3800,14 +3805,62 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
category: "Vector",
|
||||
node_template: NodeTemplate {
|
||||
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![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode<_>")),
|
||||
..Default::default()
|
||||
},
|
||||
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()],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(NodeGraphMessage::SendSelectedNodes);
|
||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
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
|
||||
|
|
@ -203,8 +204,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
NodeGraphMessage::DeleteNodes { node_ids, delete_children } => {
|
||||
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.
|
||||
// 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 {
|
||||
node_ids: selected_nodes.selected_nodes().cloned().collect::<Vec<_>>(),
|
||||
delete_children,
|
||||
})
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
NodeGraphMessage::DisconnectInput { input_connector } => {
|
||||
network_interface.disconnect_input(&input_connector, selection_network_path);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ pub struct DocumentMetadata {
|
|||
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
|
||||
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
pub vector_modify: HashMap<NodeId, VectorData>,
|
||||
// TODO: Remove and derive from document_ptz in document message handler
|
||||
/// Transform from document space to viewport space.
|
||||
pub document_to_viewport: DAffine2,
|
||||
}
|
||||
|
|
@ -52,7 +51,7 @@ impl DocumentMetadata {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
@ -71,29 +70,15 @@ 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
|
||||
pub fn transform_to_document(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
self.document_to_viewport.inverse() * self.transform_to_viewport(layer)
|
||||
}
|
||||
|
||||
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
layer
|
||||
.ancestors(self)
|
||||
.filter_map(|ancestor_layer| {
|
||||
if ancestor_layer != LayerNodeIdentifier::ROOT_PARENT {
|
||||
self.upstream_transforms.get(&ancestor_layer.to_node())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.copied()
|
||||
.map(|(footprint, transform)| footprint.transform * transform)
|
||||
.next()
|
||||
self.upstream_transforms
|
||||
.get(&layer.to_node())
|
||||
.map(|(footprint, transform)| footprint.transform * *transform)
|
||||
.unwrap_or(self.document_to_viewport)
|
||||
}
|
||||
|
||||
|
|
@ -119,16 +104,9 @@ 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
|
||||
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.click_targets
|
||||
.get(&layer)?
|
||||
self.click_targets(layer)?
|
||||
.iter()
|
||||
.filter_map(|click_target| click_target.subpath().bounding_box_with_transform(transform))
|
||||
.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::{concrete, Type};
|
||||
use graphene_std::renderer::{ClickTarget, Quad};
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
|
||||
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");
|
||||
return None;
|
||||
};
|
||||
match &node_metadata.persistent_metadata.node_type_metadata.clone() {
|
||||
match &node_metadata.persistent_metadata.node_type_metadata {
|
||||
NodeTypePersistentMetadata::Layer(layer_metadata) => {
|
||||
match layer_metadata.position {
|
||||
LayerPosition::Absolute(position) => Some(position),
|
||||
|
|
@ -2550,8 +2551,7 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
|
||||
pub fn set_document_to_viewport_transform(&mut self, transform: DAffine2) {
|
||||
let document_metadata = self.document_metadata_mut();
|
||||
document_metadata.document_to_viewport = transform;
|
||||
self.document_metadata.document_to_viewport = transform;
|
||||
}
|
||||
|
||||
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 {
|
||||
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() {
|
||||
|
|
@ -2902,7 +2902,7 @@ impl NodeNetworkInterface {
|
|||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
pub fn document_metadata_mut(&mut self) -> &mut DocumentMetadata {
|
||||
&mut self.document_metadata
|
||||
/// Update the cached transforms of the layers
|
||||
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
|
||||
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 {
|
||||
log::error!("Could not get nested network in set_implementation");
|
||||
return;
|
||||
|
|
@ -3095,6 +3106,20 @@ impl NodeNetworkInterface {
|
|||
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
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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 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 {
|
||||
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;
|
||||
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(), &[]);
|
||||
|
||||
|
|
@ -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"
|
||||
if reference == "Artboard" {
|
||||
let label = document.network_interface.display_name(node_id, &[]);
|
||||
|
|
|
|||
|
|
@ -391,6 +391,9 @@ impl SelectToolData {
|
|||
})
|
||||
.collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
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_count = selected_layers_count;
|
||||
|
||||
// Outline selected layers
|
||||
// Outline selected layers, but not artboards
|
||||
for layer in document
|
||||
.network_interface
|
||||
.selected_nodes(&[])
|
||||
.unwrap()
|
||||
.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));
|
||||
}
|
||||
|
|
@ -429,7 +433,7 @@ impl Fsm for SelectToolFsmState {
|
|||
.selected_nodes(&[])
|
||||
.unwrap()
|
||||
.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));
|
||||
let transform = transform.unwrap_or(DAffine2::IDENTITY);
|
||||
if transform.matrix2.determinant() == 0. {
|
||||
|
|
@ -440,6 +444,7 @@ impl Fsm for SelectToolFsmState {
|
|||
.selected_nodes(&[])
|
||||
.unwrap()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.filter_map(|layer| {
|
||||
document
|
||||
.metadata()
|
||||
|
|
@ -649,7 +654,6 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
|
||||
if let Some(intersection) = intersection {
|
||||
|
||||
tool_data.layer_selected_on_start = Some(intersection);
|
||||
selected = intersection_list;
|
||||
|
||||
|
|
@ -694,7 +698,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let axis_align = input.keyboard.key(modifier_keys.axis_align);
|
||||
|
||||
// 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 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) {
|
||||
tool_data.layers_dragging.append(&mut vec![document
|
||||
.find_deepest(&selected)
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next().expect("Child should exist when dragging deepest"))]);
|
||||
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(
|
||||
LayerNodeIdentifier::ROOT_PARENT
|
||||
.children(document.metadata())
|
||||
.next()
|
||||
.expect("ROOT_PARENT should have a layer child when clicking"),
|
||||
)]);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||
nodes: tool_data
|
||||
.layers_dragging
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
*mouse_position = input.mouse.position;
|
||||
*start_mouse = input.mouse.position;
|
||||
selected.original_transforms.clear();
|
||||
|
||||
selected.responses.add(DocumentMessage::StartTransaction);
|
||||
};
|
||||
|
||||
match message {
|
||||
|
|
@ -97,6 +99,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
|
@ -156,6 +159,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
TransformLayerMessage::ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
use crate::consts::FILE_SAVE_SUFFIX;
|
||||
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::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
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::graphene_compiler::Compiler;
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||
use graphene_core::memo::IORecord;
|
||||
use graphene_core::raster::ImageFrame;
|
||||
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
|
||||
use graphene_core::renderer::{GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
|
||||
use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
|
||||
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::VectorData;
|
||||
use graphene_core::{Color, GraphicElement, SurfaceFrame};
|
||||
use graphene_std::renderer::format_transform_matrix;
|
||||
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
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
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
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.
|
||||
|
|
@ -83,9 +73,6 @@ pub struct ExecutionResponse {
|
|||
execution_id: u64,
|
||||
result: Result<TaggedValue, String>,
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
@ -144,9 +131,6 @@ impl NodeRuntime {
|
|||
monitor_nodes: Vec::new(),
|
||||
|
||||
thumbnail_renders: Default::default(),
|
||||
click_targets: HashMap::new(),
|
||||
vector_modify: HashMap::new(),
|
||||
upstream_transforms: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,9 +210,6 @@ impl NodeRuntime {
|
|||
execution_id,
|
||||
result,
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
@ -300,28 +281,9 @@ impl NodeRuntime {
|
|||
};
|
||||
|
||||
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>>() {
|
||||
Self::process_graphic_element(&mut self.thumbnail_renders, &mut self.click_targets, 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);
|
||||
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -330,16 +292,11 @@ impl NodeRuntime {
|
|||
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
fn process_graphic_element(
|
||||
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
|
||||
click_targets: &mut HashMap<NodeId, Vec<ClickTarget>>,
|
||||
parent_network_node_id: NodeId,
|
||||
graphic_element: &impl GraphicElementRendered,
|
||||
responses: &mut VecDeque<FrontendMessage>,
|
||||
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
|
||||
|
||||
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> {
|
||||
let TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) = node_graph_output else {
|
||||
return Err("Incorrect render type for exportign (expected RenderOutput::Svg)".to_string());
|
||||
let TaggedValue::RenderOutput(RenderOutput {
|
||||
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 {
|
||||
|
|
@ -572,10 +533,7 @@ impl NodeGraphExecutor {
|
|||
let ExecutionResponse {
|
||||
execution_id,
|
||||
result,
|
||||
new_click_targets,
|
||||
responses: existing_responses,
|
||||
new_vector_modify,
|
||||
new_upstream_transforms,
|
||||
transform,
|
||||
} = execution_response;
|
||||
|
||||
|
|
@ -585,15 +543,13 @@ impl NodeGraphExecutor {
|
|||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
// 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}"));
|
||||
}
|
||||
};
|
||||
|
||||
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())?;
|
||||
if let Some(export_config) = execution_context.export_config {
|
||||
|
|
@ -608,7 +564,10 @@ impl NodeGraphExecutor {
|
|||
let type_delta = match result {
|
||||
Err(e) => {
|
||||
// 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}");
|
||||
|
||||
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> {
|
||||
match node_graph_output {
|
||||
TaggedValue::SurfaceFrame(SurfaceFrame { .. }) => {
|
||||
// TODO: Reimplement this now that document-legacy is gone
|
||||
}
|
||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
}
|
||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
|
||||
let matrix = format_transform_matrix(frame.transform);
|
||||
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) };
|
||||
let svg = format!(
|
||||
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 });
|
||||
TaggedValue::RenderOutput(render_output) => {
|
||||
let RenderMetadata {
|
||||
footprints,
|
||||
click_targets,
|
||||
vector_data,
|
||||
} = render_output.metadata;
|
||||
|
||||
match render_output.data {
|
||||
graphene_std::wasm_application_io::RenderOutputType::Svg(svg) => {
|
||||
// Send to frontend
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||
}
|
||||
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
|
||||
let matrix = format_transform_matrix(frame.transform);
|
||||
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{}\"", matrix) };
|
||||
let svg = format!(
|
||||
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::RenderRulers);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
|
||||
|
|
|
|||
|
|
@ -499,8 +499,8 @@
|
|||
|
||||
// Dimming
|
||||
&.selected {
|
||||
// Halfway 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);
|
||||
// 1/3 between 3-darkgray and 4-dimgray (this interpolation approach only works on grayscale values)
|
||||
--component: calc((Max(var(--color-3-darkgray-rgb)) * 2 + Max(var(--color-4-dimgray-rgb))) / 3);
|
||||
background: rgb(var(--component), var(--component), var(--component));
|
||||
|
||||
&.full-highlight {
|
||||
|
|
|
|||
|
|
@ -622,11 +622,13 @@ impl EditorHandle {
|
|||
self.dispatch(message);
|
||||
|
||||
let id = NodeId(id);
|
||||
let message = NodeGraphMessage::DeleteNodes {
|
||||
self.dispatch(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![id],
|
||||
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
|
||||
|
|
@ -733,7 +735,7 @@ impl EditorHandle {
|
|||
for node_id in nodes_to_upgrade {
|
||||
document
|
||||
.network_interface
|
||||
.set_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
|
||||
.replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>"));
|
||||
document
|
||||
.network_interface
|
||||
.add_input(&node_id, &[], TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string());
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use crate::application_io::TextureFrame;
|
||||
use crate::raster::{BlendMode, ImageFrame};
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::uuid::NodeId;
|
||||
use crate::vector::VectorData;
|
||||
use crate::{Color, Node};
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ impl AlphaBlending {
|
|||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<GraphicElement>,
|
||||
elements: Vec<(GraphicElement, Option<NodeId>)>,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
|
@ -64,7 +65,7 @@ impl GraphicGroup {
|
|||
|
||||
pub fn new(elements: Vec<GraphicElement>) -> Self {
|
||||
Self {
|
||||
elements,
|
||||
elements: elements.into_iter().map(|element| (element, None)).collect(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
}
|
||||
|
|
@ -216,7 +217,7 @@ impl Artboard {
|
|||
#[derive(Clone, Default, Debug, Hash, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ArtboardGroup {
|
||||
pub artboards: Vec<Artboard>,
|
||||
pub artboards: Vec<(Artboard, Option<NodeId>)>,
|
||||
}
|
||||
|
||||
impl ArtboardGroup {
|
||||
|
|
@ -226,14 +227,15 @@ impl ArtboardGroup {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn add_artboard(&mut self, artboard: Artboard) {
|
||||
self.artboards.push(artboard);
|
||||
fn add_artboard(&mut self, artboard: Artboard, node_id: Option<NodeId>) {
|
||||
self.artboards.push((artboard, node_id));
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ConstructLayerNode<Stack, GraphicElement> {
|
||||
pub struct ConstructLayerNode<Stack, GraphicElement, NodePath> {
|
||||
stack: Stack,
|
||||
graphic_element: GraphicElement,
|
||||
node_path: NodePath,
|
||||
}
|
||||
|
||||
#[node_fn(ConstructLayerNode)]
|
||||
|
|
@ -241,6 +243,7 @@ async fn construct_layer<Data: Into<GraphicElement> + Send>(
|
|||
footprint: crate::transform::Footprint,
|
||||
mut stack: impl Node<crate::transform::Footprint, Output = GraphicGroup>,
|
||||
graphic_element: impl Node<crate::transform::Footprint, Output = Data>,
|
||||
node_path: Vec<NodeId>,
|
||||
) -> GraphicGroup {
|
||||
let graphic_element = self.graphic_element.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.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
|
||||
}
|
||||
|
||||
|
|
@ -301,17 +306,25 @@ async fn construct_artboard(
|
|||
clip,
|
||||
}
|
||||
}
|
||||
pub struct AddArtboardNode<ArtboardGroup, Artboard> {
|
||||
pub struct AddArtboardNode<ArtboardGroup, Artboard, NodePath> {
|
||||
artboards: ArtboardGroup,
|
||||
artboard: Artboard,
|
||||
node_path: NodePath,
|
||||
}
|
||||
|
||||
#[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 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
|
||||
}
|
||||
|
|
@ -338,7 +351,7 @@ impl From<GraphicGroup> for GraphicElement {
|
|||
}
|
||||
|
||||
impl Deref for GraphicGroup {
|
||||
type Target = Vec<GraphicElement>;
|
||||
type Target = Vec<(GraphicElement, Option<NodeId>)>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.elements
|
||||
}
|
||||
|
|
@ -363,7 +376,7 @@ where
|
|||
{
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
elements: (vec![value.into()]),
|
||||
elements: (vec![(value.into(), None)]),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,26 @@ pub use quad::Quad;
|
|||
pub use rect::Rect;
|
||||
|
||||
use crate::raster::{BlendMode, Image, ImageFrame};
|
||||
use crate::transform::Transform;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::transform::{Footprint, Transform};
|
||||
use crate::uuid::{generate_uuid, NodeId};
|
||||
use crate::vector::style::{Fill, Stroke, ViewMode};
|
||||
use crate::vector::PointId;
|
||||
use crate::Raster;
|
||||
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
use base64::Engine;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use num_traits::Zero;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Write;
|
||||
#[cfg(feature = "vello")]
|
||||
use vello::*;
|
||||
|
||||
/// Represents a clickable target for the layer
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ClickTarget {
|
||||
subpath: bezier_rs::Subpath<PointId>,
|
||||
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)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
|
||||
|
||||
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")]
|
||||
fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene {
|
||||
let mut scene = vello::Scene::new();
|
||||
self.render_to_vello(&mut scene, transform, context);
|
||||
scene
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {}
|
||||
|
||||
|
|
@ -305,7 +326,7 @@ impl GraphicElementRendered for GraphicGroup {
|
|||
}
|
||||
},
|
||||
|render| {
|
||||
for element in self.iter() {
|
||||
for (element, _) in self.iter() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
},
|
||||
|
|
@ -313,16 +334,35 @@ impl GraphicElementRendered for GraphicGroup {
|
|||
}
|
||||
|
||||
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>) {
|
||||
for element in self.elements.iter() {
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
||||
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();
|
||||
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() {
|
||||
click_target.apply_transform(element.transform())
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
for element in self.iter() {
|
||||
|
||||
for (element, _) in self.iter() {
|
||||
element.render_to_vello(scene, child_transform, context);
|
||||
}
|
||||
|
||||
if layer {
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
||||
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 filled = self.style.fill() != &Fill::None;
|
||||
let fill = |mut subpath: bezier_rs::Subpath<_>| {
|
||||
|
|
@ -558,21 +622,13 @@ impl GraphicElementRendered for Artboard {
|
|||
"g",
|
||||
// Group tag 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 {
|
||||
let id = format!("artboard-{}", generate_uuid());
|
||||
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!(
|
||||
&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
|
||||
)
|
||||
.unwrap();
|
||||
|
|
@ -581,9 +637,7 @@ impl GraphicElementRendered for Artboard {
|
|||
},
|
||||
// Artboard contents
|
||||
|render| {
|
||||
for element in self.graphic_group.iter() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
self.graphic_group.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")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
|
||||
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 {
|
||||
true
|
||||
}
|
||||
|
|
@ -636,24 +701,30 @@ impl GraphicElementRendered for Artboard {
|
|||
|
||||
impl GraphicElementRendered for crate::ArtboardGroup {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
for artboard in &self.artboards {
|
||||
artboard.add_click_targets(click_targets);
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||
for (artboard, element_id) in &self.artboards {
|
||||
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")]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
||||
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);
|
||||
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())
|
||||
}
|
||||
|
||||
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);
|
||||
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 {
|
||||
GraphicElement::VectorData(vector_data) => vector_data.add_click_targets(click_targets),
|
||||
GraphicElement::Raster(raster) => raster.add_click_targets(click_targets),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.add_click_targets(click_targets),
|
||||
GraphicElement::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
|
||||
GraphicElement::Raster(raster) => raster.collect_metadata(metadata, footprint, element_id),
|
||||
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]> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for Option<Color> {
|
||||
|
|
@ -911,8 +1008,6 @@ impl GraphicElementRendered for Option<Color> {
|
|||
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for Vec<Color> {
|
||||
|
|
@ -934,8 +1029,6 @@ impl GraphicElementRendered for Vec<Color> {
|
|||
fn bounding_box(&self, _transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
None
|
||||
}
|
||||
|
||||
fn add_click_targets(&self, _click_targets: &mut Vec<ClickTarget>) {}
|
||||
}
|
||||
|
||||
/// A segment of an svg string to allow for embedding blob urls
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ impl TransformMut for VectorData {
|
|||
|
||||
impl Transform for Artboard {
|
||||
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 {
|
||||
self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
pub struct Uuid(
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -742,6 +742,12 @@ impl PathStyle {
|
|||
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.
|
||||
///
|
||||
/// # Example
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ pub struct VectorData {
|
|||
pub point_domain: PointDomain,
|
||||
pub segment_domain: SegmentDomain,
|
||||
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 {
|
||||
|
|
@ -52,6 +55,7 @@ impl VectorData {
|
|||
point_domain: PointDomain::new(),
|
||||
segment_domain: SegmentDomain::new(),
|
||||
region_domain: RegionDomain::new(),
|
||||
upstream_graphic_group: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub struct AssignColorsNode<Fill, Stroke, Gradient, Reverse, Randomize, Seed, Re
|
|||
#[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 {
|
||||
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());
|
||||
|
||||
assign_colors(
|
||||
|
|
@ -298,9 +298,9 @@ pub trait ConcatElement {
|
|||
impl ConcatElement for GraphicGroup {
|
||||
fn concat(&mut self, other: &Self, transform: DAffine2) {
|
||||
// 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();
|
||||
self.push(element);
|
||||
self.push((element, footprint_mapping));
|
||||
}
|
||||
self.alpha_blending = other.alpha_blending;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
|
|||
use dyn_any::{DynAny, StaticType};
|
||||
use graphene_core::memo::MemoHashGuard;
|
||||
pub use graphene_core::uuid::generate_uuid;
|
||||
pub use graphene_core::uuid::NodeId;
|
||||
use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type};
|
||||
pub mod value;
|
||||
|
||||
use glam::IVec2;
|
||||
use log::Metadata;
|
||||
|
|
@ -13,25 +15,6 @@ use std::collections::hash_map::DefaultHasher;
|
|||
use std::collections::HashMap;
|
||||
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.
|
||||
/// 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 {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
use super::DocumentNode;
|
||||
use crate::document::NodeId;
|
||||
pub use crate::imaginate_input::{ImaginateCache, ImaginateController, ImaginateMaskStartingFill, ImaginateSamplingMethod};
|
||||
use crate::proto::{Any as DAny, FutureAny};
|
||||
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;
|
||||
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};
|
||||
use std::fmt::Display;
|
||||
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)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum RenderOutput {
|
||||
pub enum RenderOutputType {
|
||||
CanvasFrame(graphene_core::SurfaceFrame),
|
||||
Svg(String),
|
||||
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)
|
||||
trait FakeHash {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ fn boolean_operation_node(group_of_paths: GraphicGroup, operation: BooleanOperat
|
|||
|
||||
fn collect_vector_data(graphic_group: &GraphicGroup) -> Vec<VectorData> {
|
||||
// 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
|
||||
let transformed_vector_data = vector_data.map(|mut vector_data| {
|
||||
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
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
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::Image;
|
||||
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::transform::Footprint;
|
||||
use graphene_core::Node;
|
||||
|
|
@ -16,6 +18,7 @@ use graphene_core::{Color, WasmNotSend};
|
|||
use base64::Engine;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use glam::DAffine2;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::Clamped;
|
||||
|
|
@ -86,7 +89,7 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
|||
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 {
|
||||
render.leaf_tag("rect", |attributes| {
|
||||
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);
|
||||
|
||||
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_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;
|
||||
|
||||
if let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() {
|
||||
use vello::*;
|
||||
|
||||
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 {
|
||||
let footprint = render_config.viewport;
|
||||
let Some(exec) = editor.application_io.as_ref().unwrap().gpu_executor() else {
|
||||
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 {
|
||||
surface_id: surface_handle.window_id,
|
||||
resolution: render_config.viewport.resolution,
|
||||
transform: glam::DAffine2::IDENTITY,
|
||||
};
|
||||
RenderOutput::CanvasFrame(frame)
|
||||
|
||||
RenderOutputType::CanvasFrame(frame)
|
||||
}
|
||||
|
||||
#[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"))]
|
||||
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;
|
||||
match output_format {
|
||||
let data = match output_format {
|
||||
ExportFormat::Svg => render_svg(data, SvgRender::new(), render_params, footprint),
|
||||
ExportFormat::Canvas => {
|
||||
if use_vello && editor_api.application_io.as_ref().unwrap().gpu_executor().is_some() {
|
||||
#[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")))]
|
||||
render_svg(data, SvgRender::new(), render_params, footprint)
|
||||
} else {
|
||||
|
|
@ -239,5 +253,6 @@ async fn render_node<'a: 'input, T: 'input + GraphicElementRendered + WasmNotSen
|
|||
}
|
||||
}
|
||||
_ => 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::proto::{NodeConstructor, TypeErasedBox};
|
||||
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::application_io::{RenderConfig, TextureFrame};
|
||||
use graphene_std::raster::*;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::style::GradientStops;
|
||||
use graphene_std::wasm_application_io::*;
|
||||
use graphene_std::GraphicElement;
|
||||
|
|
@ -26,7 +29,6 @@ use graphene_std::GraphicElement;
|
|||
use wgpu_executor::{CommandBuffer, ShaderHandle, ShaderInputFrame, WgpuExecutor, WgpuShaderInput};
|
||||
use wgpu_executor::{WgpuSurface, WindowHandle};
|
||||
|
||||
use dyn_any::StaticType;
|
||||
use glam::{DAffine2, DVec2, UVec2};
|
||||
use once_cell::sync::Lazy;
|
||||
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::text::TextGeneratorNode<_, _, _>, input: &WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
||||
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: ImageFrame<Color>, 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: 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::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();
|
||||
for (id, c, types) in node_types.into_iter().flatten() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue