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

* add NodeId (u64) and Footprint to Graphic Group

* Render Output footprints

* Small bug fixes

* Commented out render output click targets/footprints

* Run graph when deleting

* Switch to node path

* Add upstream clicktargets for boolean operation

* Fix boolean operations

* Fix grouped layers

* Add click targets to vello render

* Add cache to artwork

* Fix demo artwork

* Improve recursion

* Code review

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-09-16 01:26:59 +00:00
committed by GitHub
co-authored by Dennis Kobert Keavon Chambers
parent ef007736f5
commit ca0d102296
28 changed files with 1003 additions and 670 deletions
@@ -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),
+47 -76
View File
@@ -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),