mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 01:08:12 +08:00
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:
co-authored by
Dennis Kobert
Keavon Chambers
parent
ef007736f5
commit
ca0d102296
@@ -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)
|
||||
}
|
||||
|
||||
+3
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user