diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 32af59cb..533dde5b 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -6,6 +6,7 @@ use graphene_core::text::Font; #[derive(Debug, Default)] pub struct Dispatcher { + buffered_queue: Option>>, message_queues: Vec>, pub responses: Vec, pub message_handlers: DispatcherMessageHandlers, @@ -65,8 +66,18 @@ impl Dispatcher { pub fn handle_message>(&mut self, message: T) { self.message_queues.push(VecDeque::from_iter([message.into()])); - while let Some(message) = self.message_queues.last_mut().and_then(VecDeque::pop_front) { + // Do not buffer the EndBuffer message + if !matches!(message, Message::EndBuffer(_)) { + if let Some(buffered_queue) = &mut self.buffered_queue { + // Store each message in a deque so that its children are added before future messages + let mut message_deque = VecDeque::new(); + message_deque.push_back(message); + buffered_queue.push(message_deque); + continue; + } + } + // Skip processing of this message if it will be processed later (at the end of the shallowest level queue) if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) { let already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some(); @@ -90,6 +101,25 @@ impl Dispatcher { // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend match message { + Message::StartBuffer => { + self.buffered_queue = Some(std::mem::take(&mut self.message_queues)); + } + Message::EndBuffer(render_metadata) => { + // The buffered vec is added before the metadata messages, because the end of the vec is processed first + if let Some(buffered_queue) = self.buffered_queue.take() { + self.message_queues.extend(buffered_queue); + }; + + let graphene_std::renderer::RenderMetadata { footprints, click_targets } = render_metadata; + + let mut update_upstream_transform = VecDeque::new(); + update_upstream_transform.push_back(DocumentMessage::UpdateUpstreamTransforms { upstream_transforms: footprints }.into()); + self.message_queues.push(update_upstream_transform); + + let mut update_click_targets = VecDeque::new(); + update_click_targets.push_back(DocumentMessage::UpdateClickTargets { click_targets }.into()); + self.message_queues.push(update_click_targets); + } Message::NoOp => {} Message::Init => { // Load persistent data from the browser database diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index cacb4295..efad5063 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -30,11 +30,12 @@ impl MessageHandler for NewDocumentDialogMessageHa id: NodeId(generate_uuid()), artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()), }); - responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); } responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::UpdateNewNodeGraph); + responses.add(Message::StartBuffer); + responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll); } } diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index cb6de732..1c84c986 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -8,6 +8,8 @@ pub enum Message { NoOp, Init, Batched(Box<[Message]>), + StartBuffer, + EndBuffer(graphene_std::renderer::RenderMetadata), #[child] Broadcast(BroadcastMessage), diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index d7da13f1..cfe6b081 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1028,7 +1028,7 @@ impl MessageHandler> for DocumentMessag return; } - self.document_undo_history.pop_back(); + self.undo(ipp, responses); self.network_interface.finish_transaction(); responses.add(OverlaysMessage::Draw); } diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 43ce4248..f4b4a38b 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -67,10 +67,8 @@ impl MessageHandler> for Gr transform_in, skip_rerender, } => { - let parent_transform = network_interface.document_metadata().downstream_transform_to_viewport(layer); - let current_transform = Some(network_interface.document_metadata().transform_to_viewport(layer)); if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { - modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender); + modify_inputs.transform_set(transform, transform_in, skip_rerender); } } GraphOperationMessage::TransformSetPivot { layer, pivot } => { @@ -191,6 +189,7 @@ impl MessageHandler> for Gr let layer = modify_inputs.create_layer(id); modify_inputs.insert_text(text, font, size, layer); network_interface.move_layer_to_stack(layer, parent, insert_index, &[]); + responses.add(GraphOperationMessage::StrokeSet { layer, stroke: Stroke::default() }); responses.add(NodeGraphMessage::RunDocumentGraph); } GraphOperationMessage::ResizeArtboard { layer, location, dimensions } => { diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 01d71075..1fdb0859 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -15,6 +15,7 @@ use graphene_core::vector::{PointId, VectorModificationType}; use graphene_core::{Artboard, Color}; use glam::{DAffine2, DVec2, IVec2}; +use graphene_std::vector::VectorData; #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] pub enum TransformIn { @@ -145,9 +146,12 @@ impl<'a> ModifyInputsContext<'a> { } pub fn insert_vector_data(&mut self, subpaths: Vec>, layer: LayerNodeIdentifier) { + let vector_data = VectorData::from_subpaths(subpaths, true); + let path = resolve_document_node_type("Path") .expect("Path node does not exist") - .node_template_input_override([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))]); + .node_template_input_override([Some(NodeInput::value(TaggedValue::VectorData(vector_data), false))]); + let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template(); let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_node_template(); let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_node_template(); @@ -195,8 +199,6 @@ impl<'a> ModifyInputsContext<'a> { let stroke_id = NodeId(generate_uuid()); self.network_interface.insert_node(stroke_id, stroke, &[]); self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]); - - self.responses.add(NodeGraphMessage::RunDocumentGraph); } pub fn insert_image_data(&mut self, image_frame: ImageFrame, layer: LayerNodeIdentifier) { @@ -334,22 +336,15 @@ impl<'a> ModifyInputsContext<'a> { } } - pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option, skip_rerender: bool) { - let Some(transform_node_id) = self.existing_node_id("Transform") else { return }; - let upstream_transform = self.network_interface.document_metadata().upstream_transform(transform_node_id); - let to = match transform_in { - TransformIn::Local => DAffine2::IDENTITY, - TransformIn::Scope { scope } => scope * parent_transform, - TransformIn::Viewport => parent_transform, + pub fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, skip_rerender: bool) { + let final_transform = match transform_in { + TransformIn::Local => DAffine2::IDENTITY * transform, + TransformIn::Scope { scope } => scope * transform, + TransformIn::Viewport => self.network_interface.document_metadata().downstream_transform_to_viewport(self.layer_node.unwrap()).inverse() * transform, }; - if current_transform - .filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.) - .is_some() - { - transform *= upstream_transform.inverse(); - } - let final_transform = to.inverse() * transform; + let Some(transform_node_id) = self.existing_node_id("Transform") else { return }; + transform_utils::update_transform(self.network_interface, &transform_node_id, final_transform); self.responses.add(PropertiesPanelMessage::Refresh); diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index 263a1bd4..03fa0773 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -196,6 +196,19 @@ impl ArtboardToolData { location: position.round().as_ivec2(), dimensions: size.round().as_ivec2(), }); + + // TODO: Resize artboard children when resizing left/top edges so that they stay in the same viewport space + // let old_top_left = bounds.bounds[0].round().as_ivec2(); + // let new_top_left = position.round().as_ivec2(); + // let top_left_delta = new_top_left - old_top_left; + // if top_left_delta != IVec2::ZERO { + // responses.add(GraphOperationMessage::TransformChange { + // layer: self.selected_artboard.unwrap(), + // transform: DAffine2::from_translation((-top_left_delta).into()), + // transform_in: TransformIn::Local, + // skip_rerender: false, + // }); + // } } } diff --git a/editor/src/messages/tool/tool_messages/ellipse_tool.rs b/editor/src/messages/tool/tool_messages/ellipse_tool.rs index 690d13f9..4e06a23e 100644 --- a/editor/src/messages/tool/tool_messages/ellipse_tool.rs +++ b/editor/src/messages/tool/tool_messages/ellipse_tool.rs @@ -206,6 +206,7 @@ impl Fsm for EllipseToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses); + responses.add(Message::StartBuffer); responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index 5fd2f313..6ea841cc 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -229,15 +229,11 @@ impl Fsm for FreehandToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses); + responses.add(Message::StartBuffer); tool_options.fill.apply_fill(layer, responses); tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_data.layer = Some(layer); - let transform = document.metadata().transform_to_viewport(parent); - let position = transform.inverse().transform_point2(input.mouse.position); - - extend_path_with_next_segment(tool_data, position, responses); - FreehandToolFsmState::Drawing } (FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => { diff --git a/editor/src/messages/tool/tool_messages/line_tool.rs b/editor/src/messages/tool/tool_messages/line_tool.rs index 2943193e..5808d62e 100644 --- a/editor/src/messages/tool/tool_messages/line_tool.rs +++ b/editor/src/messages/tool/tool_messages/line_tool.rs @@ -188,6 +188,7 @@ impl Fsm for LineToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses); + responses.add(Message::StartBuffer); responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 2821cd7f..934c8a12 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -525,21 +525,11 @@ impl Fsm for PenToolFsmState { tool_data.layer = Some(layer); tool_data.next_point = position; tool_data.next_handle_start = position; - } else { - // New path layer - let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); - let nodes = vec![(NodeId(0), node_type.default_node_template())]; - - let parent = document.new_layer_parent(true); - let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses); - tool_options.fill.apply_fill(layer, responses); - tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); - tool_data.layer = Some(layer); - + } else if let Some(layer) = tool_data.layer { + // Add the first point to a new layer // Generate first point let id = PointId::generate(); - let transform = document.metadata().transform_to_document(parent); - let pos = transform.inverse().transform_point2(snapped.snapped_point_document); + let pos = document.metadata().transform_to_viewport(layer).inverse().transform_point2(viewport); let modification_type = VectorModificationType::InsertPoint { id, position: pos }; responses.add(GraphOperationMessage::Vector { layer, modification_type }); tool_data.add_point(LastPoint { @@ -550,9 +540,21 @@ impl Fsm for PenToolFsmState { }); tool_data.next_point = pos; tool_data.next_handle_start = pos; + } else { + // New path layer + let node_type = resolve_document_node_type("Path").expect("Path node does not exist"); + let nodes = vec![(NodeId(0), node_type.default_node_template())]; + + let parent = document.new_layer_parent(true); + let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses); + tool_options.fill.apply_fill(layer, responses); + tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses); + tool_data.layer = Some(layer); + responses.add(Message::StartBuffer); + responses.add(PenToolMessage::DragStart); + return PenToolFsmState::Ready; } tool_data.handle_end = None; - // Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle PenToolFsmState::DraggingHandle } diff --git a/editor/src/messages/tool/tool_messages/polygon_tool.rs b/editor/src/messages/tool/tool_messages/polygon_tool.rs index 96e076c7..66b415e9 100644 --- a/editor/src/messages/tool/tool_messages/polygon_tool.rs +++ b/editor/src/messages/tool/tool_messages/polygon_tool.rs @@ -265,6 +265,7 @@ impl Fsm for PolygonToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses); + responses.add(Message::StartBuffer); responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), diff --git a/editor/src/messages/tool/tool_messages/rectangle_tool.rs b/editor/src/messages/tool/tool_messages/rectangle_tool.rs index 7684c6b2..87b91cf9 100644 --- a/editor/src/messages/tool/tool_messages/rectangle_tool.rs +++ b/editor/src/messages/tool/tool_messages/rectangle_tool.rs @@ -212,6 +212,7 @@ impl Fsm for RectangleToolFsmState { let nodes = vec![(NodeId(0), node)]; let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses); + responses.add(Message::StartBuffer); responses.add(GraphOperationMessage::TransformSet { layer, transform: DAffine2::from_scale_angle_translation(DVec2::ONE, 0., input.mouse.position), diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs index da6558db..ddf33b53 100644 --- a/editor/src/messages/tool/tool_messages/spline_tool.rs +++ b/editor/src/messages/tool/tool_messages/spline_tool.rs @@ -205,14 +205,6 @@ impl Fsm for SplineToolFsmState { responses.add(DocumentMessage::DeselectAllLayers); let parent = document.new_layer_parent(true); - let transform = document.metadata().transform_to_viewport(parent); - - let snapped_position = input.mouse.position; - - let pos = transform.inverse().transform_point2(snapped_position); - - tool_data.points.push(pos); - tool_data.next_point = pos; tool_data.weight = tool_options.line_weight; @@ -225,6 +217,8 @@ impl Fsm for SplineToolFsmState { tool_options.stroke.apply_stroke(tool_data.weight, layer, responses); tool_data.layer = Some(layer); + responses.add(Message::StartBuffer); + SplineToolFsmState::Drawing } (SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => { @@ -237,11 +231,9 @@ impl Fsm for SplineToolFsmState { let transform = document.metadata().transform_to_viewport(layer); let pos = transform.inverse().transform_point2(snapped_position); - if let Some(last_pos) = tool_data.points.last() { - if last_pos.distance(pos) > DRAG_THRESHOLD { - tool_data.points.push(pos); - tool_data.next_point = pos; - } + if tool_data.points.last().map_or(true, |last_pos| last_pos.distance(pos) > DRAG_THRESHOLD) { + tool_data.points.push(pos); + tool_data.next_point = pos; } update_spline(document, tool_data, true, responses); diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index a68afe74..ab0540d8 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -213,11 +213,7 @@ struct TextToolData { impl TextToolData { /// Set the editing state of the currently modifying layer - fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque) { - if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network_interface) { - responses.add(NodeGraphMessage::SetVisibility { node_id, visible: !editable }); - } - + fn set_editing(&self, editable: bool, font_cache: &FontCache, responses: &mut VecDeque) { if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) { responses.add(FrontendMessage::DisplayEditableTextbox { text: editing_text.text.clone(), @@ -229,6 +225,8 @@ impl TextToolData { }); } else { responses.add(FrontendMessage::DisplayRemoveEditableTextbox); + // Clear all selected nodes when no longer editing + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: Vec::new() }); } } @@ -253,17 +251,23 @@ impl TextToolData { } if tool_state == TextToolFsmState::Editing { - self.set_editing(false, font_cache, document, responses); + self.set_editing(false, font_cache, responses); } self.layer = layer; - self.load_layer_text_node(document); + if self.load_layer_text_node(document).is_some() { + responses.add(DocumentMessage::AddTransaction); - responses.add(DocumentMessage::AddTransaction); + self.set_editing(true, font_cache, responses); - self.set_editing(true, font_cache, document, responses); - - responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); + // Make the rendered text invisible while editing + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(graph_modification_utils::get_text_id(self.layer, &document.network_interface).unwrap(), 1), + input: NodeInput::value(TaggedValue::String("".to_string()), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + }; } fn interact( @@ -294,6 +298,7 @@ impl TextToolData { parent: document.new_layer_parent(true), insert_index: 0, }); + responses.add(Message::StartBuffer); responses.add(GraphOperationMessage::FillSet { layer: self.layer, fill: if editing_text.color.is_some() { Fill::Solid(editing_text.color.unwrap()) } else { Fill::None }, @@ -305,14 +310,15 @@ impl TextToolData { skip_rerender: true, }); - self.set_editing(true, font_cache, document, responses); + self.set_editing(true, font_cache, responses); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] }); + responses.add(NodeGraphMessage::RunDocumentGraph); TextToolFsmState::Editing } else { // Removing old text as editable - self.set_editing(false, font_cache, document, responses); + self.set_editing(false, font_cache, responses); TextToolFsmState::Ready } @@ -404,7 +410,7 @@ impl Fsm for TextToolFsmState { } (state, TextToolMessage::Abort) => { if state == TextToolFsmState::Editing { - tool_data.set_editing(false, font_cache, document, responses); + tool_data.set_editing(false, font_cache, responses); } TextToolFsmState::Ready @@ -415,12 +421,13 @@ impl Fsm for TextToolFsmState { TextToolFsmState::Editing } (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => { + tool_data.set_editing(false, font_cache, responses); + responses.add(NodeGraphMessage::SetInput { input_connector: InputConnector::node(graph_modification_utils::get_text_id(tool_data.layer, &document.network_interface).unwrap(), 1), input: NodeInput::value(TaggedValue::String(new_text), false), }); - - tool_data.set_editing(false, font_cache, document, responses); + responses.add(NodeGraphMessage::RunDocumentGraph); TextToolFsmState::Ready } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 695a98b4..a5a1ff7a 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -15,7 +15,8 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment}; use graphene_core::text::FontCache; use graphene_core::transform::Footprint; use graphene_core::vector::style::ViewMode; -use graphene_std::renderer::{format_transform_matrix, RenderMetadata}; +use graphene_std::renderer::format_transform_matrix; +use graphene_std::vector::VectorData; use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi}; use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta}; @@ -44,6 +45,7 @@ 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>, + vector_modify: HashMap, } /// Messages passed from the editor thread to the node runtime thread. @@ -74,6 +76,7 @@ pub struct ExecutionResponse { result: Result, responses: VecDeque, transform: DAffine2, + vector_modify: HashMap, } pub struct CompilationResponse { @@ -131,6 +134,7 @@ impl NodeRuntime { monitor_nodes: Vec::new(), thumbnail_renders: Default::default(), + vector_modify: Default::default(), } } @@ -203,6 +207,7 @@ impl NodeRuntime { let result = self.execute_network(render_config).await; let mut responses = VecDeque::new(); + // TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes self.process_monitor_nodes(&mut responses, self.update_thumbnails); self.update_thumbnails = false; @@ -211,6 +216,7 @@ impl NodeRuntime { result, responses, transform, + vector_modify: self.vector_modify.clone(), }); } } @@ -282,8 +288,18 @@ impl NodeRuntime { if let Some(io) = introspected_data.downcast_ref::>() { 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::>() { + 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::>() { 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::>() { + Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails) + } + // Insert the vector modify if we are dealing with vector data + else if let Some(record) = introspected_data.downcast_ref::>() { + self.vector_modify.insert(parent_network_node_id, record.output.clone()); + } else if let Some(record) = introspected_data.downcast_ref::>() { + self.vector_modify.insert(parent_network_node_id, record.output.clone()); } } } @@ -535,6 +551,7 @@ impl NodeGraphExecutor { result, responses: existing_responses, transform, + vector_modify, } = execution_response; responses.add(OverlaysMessage::Draw); @@ -550,6 +567,7 @@ impl NodeGraphExecutor { }; responses.extend(existing_responses.into_iter().map(Into::into)); + document.network_interface.update_vector_modify(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 { @@ -614,12 +632,6 @@ impl NodeGraphExecutor { fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { match node_graph_output { 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 @@ -638,9 +650,8 @@ impl NodeGraphExecutor { 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(Message::EndBuffer(render_output.metadata)); responses.add(DocumentMessage::RenderScrollbars); responses.add(DocumentMessage::RenderRulers); responses.add(OverlaysMessage::Draw); diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 2f37e2bf..de459e18 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -270,12 +270,13 @@ 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) } +// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes. +// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets. #[derive(Debug, Clone, PartialEq, DynAny)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct RenderMetadata { pub footprints: HashMap, pub click_targets: HashMap>, - pub vector_data: HashMap, } pub trait GraphicElementRendered { @@ -454,10 +455,14 @@ impl GraphicElementRendered for VectorData { } 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()); + + let click_targets = self + .stroke_bezier_paths() + .map(fill) + .map(|subpath| ClickTarget::new(subpath, stroke_width)) + .collect::>(); + + metadata.click_targets.insert(element_id, click_targets); } if let Some(upstream_graphic_group) = &self.upstream_graphic_group { @@ -606,14 +611,19 @@ impl GraphicElementRendered for Artboard { "g", // Group tag attributes |attributes| { + let matrix = format_transform_matrix(self.transform()); + if !matrix.is_empty() { + attributes.push("transform", matrix); + } + if self.clip { let id = format!("artboard-{}", generate_uuid()); let selector = format!("url(#{id})"); write!( &mut attributes.0.svg_defs, - r##""##, - self.dimensions.x, self.dimensions.y + r##""##, + self.dimensions.x, self.dimensions.y, ) .unwrap(); attributes.push("clip-path", selector); @@ -635,13 +645,13 @@ impl GraphicElementRendered for Artboard { } } - fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { + fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option) { 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()))); } - + footprint.transform *= self.transform(); self.graphic_group.collect_metadata(metadata, footprint, None); } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 20ea989f..0140d43c 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -225,7 +225,6 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( let mut metadata = RenderMetadata { footprints: HashMap::new(), click_targets: HashMap::new(), - vector_data: HashMap::new(), }; data.collect_metadata(&mut metadata, footprint, None);