diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 303ff572f6..51ea8be13c 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1224,21 +1224,13 @@ impl MessageHandler> for DocumentMessag .navigation_handler .calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz); self.network_interface.set_transform(transform, &self.breadcrumb_network_path); - let imports = self.network_interface.frontend_imports(&self.breadcrumb_network_path).unwrap_or_default(); - let exports = self.network_interface.frontend_exports(&self.breadcrumb_network_path).unwrap_or_default(); - let add_import = self.network_interface.frontend_import_modify(&self.breadcrumb_network_path); - let add_export = self.network_interface.frontend_export_modify(&self.breadcrumb_network_path); - + responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); responses.add(NodeGraphMessage::UpdateEdges); responses.add(NodeGraphMessage::UpdateBoxSelection); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); + responses.add(FrontendMessage::UpdateNodeGraphTransform { transform: Transform { scale: transform.matrix2.x_axis.x, diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 9c1e61dcaa..1105094e22 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -64,7 +64,6 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy> = /// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built. fn static_nodes() -> Vec { let mut custom = vec![ - // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { identifier: "Default Network", category: "General", diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 80553e730e..b5b10b63a1 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -98,6 +98,8 @@ pub enum NodeGraphMessage { shift: Key, }, PrintSelectedNodeCoordinates, + RemoveImport { import_index: usize }, + RemoveExport { export_index: usize }, RunDocumentGraph, ForceRunDocumentGraph, SelectedNodesAdd { @@ -180,6 +182,7 @@ pub enum NodeGraphMessage { }, UpdateEdges, UpdateBoxSelection, + UpdateImportsExports, UpdateLayerPanel, UpdateNewNodeGraph, UpdateTypes { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 2700c93835..e5e4084246 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -564,16 +564,26 @@ impl<'a> MessageHandler> for NodeGrap return; }; - if modify_import_export.add_export.intersect_point_no_stroke(node_graph_point) { + if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddExport); responses.add(NodeGraphMessage::SendGraph); return; - } else if modify_import_export.add_import.intersect_point_no_stroke(node_graph_point) { + } else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddImport); responses.add(NodeGraphMessage::SendGraph); return; + } else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index }); + responses.add(NodeGraphMessage::SendGraph); + return; + } else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index }); + responses.add(NodeGraphMessage::SendGraph); + return; } if network_interface @@ -1186,6 +1196,12 @@ impl<'a> MessageHandler> for NodeGrap // } // } } + NodeGraphMessage::RemoveImport { import_index: usize } => { + network_interface.remove_import(usize, selection_network_path); + } + NodeGraphMessage::RemoveExport { export_index: usize } => { + network_interface.remove_export(usize, selection_network_path); + } NodeGraphMessage::RunDocumentGraph => { responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); } @@ -1230,16 +1246,7 @@ impl<'a> MessageHandler> for NodeGrap let wires = Self::collect_wires(network_interface, breadcrumb_network_path); let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path); - let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); - let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); - let add_import = network_interface.frontend_import_modify(breadcrumb_network_path); - let add_export = network_interface.frontend_export_modify(breadcrumb_network_path); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(FrontendMessage::UpdateNodeGraph { nodes, wires }); responses.add(FrontendMessage::UpdateLayerWidths { layer_widths, @@ -1253,16 +1260,7 @@ impl<'a> MessageHandler> for NodeGrap if graph_view_overlay_open { network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path); // Send the new edges to the frontend - let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); - let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); - let add_import = network_interface.frontend_import_modify(breadcrumb_network_path); - let add_export = network_interface.frontend_export_modify(breadcrumb_network_path); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); } } NodeGraphMessage::SetInputValue { node_id, input_index, value } => { @@ -1552,6 +1550,39 @@ impl<'a> MessageHandler> for NodeGrap responses.add(FrontendMessage::UpdateBox { box_selection }) } } + NodeGraphMessage::UpdateImportsExports => { + let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); + let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); + let add_import = network_interface + .frontend_import_export_modify( + |modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::>(), + breadcrumb_network_path, + ) + .into_iter() + .next(); + let add_export = network_interface + .frontend_import_export_modify( + |modify_import_export_click_target| modify_import_export_click_target.add_import_export.input_ports().collect::>(), + breadcrumb_network_path, + ) + .into_iter() + .next(); + // let remove_imports = network_interface.frontend_import_export_modify( + // |modify_import_export_click_target| modify_import_export_click_target.remove_imports_exports.output_ports().collect::>(), + // breadcrumb_network_path, + // ); + // let remove_exports = network_interface.frontend_import_export_modify( + // |modify_import_export_click_target| modify_import_export_click_target.remove_imports_exports.input_ports().collect::>(), + // breadcrumb_network_path, + // ); + responses.add(FrontendMessage::UpdateImportsExports { + imports, + exports, + add_import, + add_export, + }); + } + NodeGraphMessage::UpdateLayerPanel => { Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses); } diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 9a19136a83..1a2372c511 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -180,6 +180,8 @@ pub struct FrontendClickTargets { pub all_nodes_bounding_box: String, #[serde(rename = "importExportsBoundingBox")] pub import_exports_bounding_box: String, + #[serde(rename = "modifyImportExport")] + pub modify_import_export: Vec, } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 95dca57036..2c4ef01116 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -837,30 +837,18 @@ impl NodeNetworkInterface { }) } - pub fn frontend_import_modify(&mut self, network_path: &[NodeId]) -> Option<(i32, i32)> { - (!network_path.is_empty()) - .then(|| { - self.modify_import_export(network_path).and_then(|modify_import_export_click_target| { - modify_import_export_click_target - .add_export - .bounding_box() - .map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32)) - }) - }) - .flatten() - } - - pub fn frontend_export_modify(&mut self, network_path: &[NodeId]) -> Option<(i32, i32)> { - (!network_path.is_empty()) - .then(|| { - self.modify_import_export(network_path).and_then(|modify_import_export_click_target| { - modify_import_export_click_target - .add_import - .bounding_box() - .map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32)) - }) + pub fn frontend_import_export_modify(&mut self, get_ports: F, network_path: &[NodeId]) -> Vec<(i32, i32)> + where + F: FnOnce(&ModifyImportExportClickTarget) -> Vec<&(usize, ClickTarget)>, + { + self.modify_import_export(network_path) + .map(|modify_import_export_click_target| { + get_ports(&modify_import_export_click_target) + .iter() + .filter_map(|(_, click_target)| click_target.bounding_box().map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32))) + .collect() }) - .flatten() + .unwrap_or_default() } pub fn height_from_click_target(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { @@ -1960,48 +1948,82 @@ impl NodeNetworkInterface { return; }; - let viewport_top_right = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(rounded_network_edge_distance.exports_to_edge_distance); - let offset_from_top_right = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(4. * GRID_SIZE as f64, 0.) - }; + let mut add_import_export = Ports::new(); + let mut remove_imports_exports = Ports::new(); - let bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_right; - let export_top_right = DVec2::new(viewport_top_right.x.max(bounding_box_top_right.x), viewport_top_right.y.min(bounding_box_top_right.y)); - let add_export_center = export_top_right + DVec2::new(0., network.exports.len() as f64 * 24.); - let add_export = ClickTarget::new(Subpath::new_ellipse(add_export_center - DVec2::new(8., 8.), add_export_center + DVec2::new(8., 8.)), 0.); + if !network_path.is_empty() { + let viewport_top_right = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(rounded_network_edge_distance.exports_to_edge_distance); + let offset_from_top_right = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(4. * GRID_SIZE as f64, 0.) + }; - let viewport_top_left = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(rounded_network_edge_distance.imports_to_edge_distance); + let bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_right; + let export_top_right: DVec2 = DVec2::new(viewport_top_right.x.max(bounding_box_top_right.x), viewport_top_right.y.min(bounding_box_top_right.y)); + let add_export_center = export_top_right + DVec2::new(0., network.exports.len() as f64 * 24.); + let add_export = ClickTarget::new( + Subpath::new_rounded_rect(add_export_center - DVec2::new(12., 12.), add_export_center + DVec2::new(12., 12.), [3.; 4]), + 0., + ); + add_import_export.insert_custom_input_port(0, add_export); - let offset_from_top_left = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(-4. * GRID_SIZE as f64, 0.) - }; + let viewport_top_left = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(rounded_network_edge_distance.imports_to_edge_distance); - let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; - let import_top_left = DVec2::new(viewport_top_left.x.min(bounding_box_top_left.x), viewport_top_left.y.min(bounding_box_top_left.y)); - let add_import_center = import_top_left + DVec2::new(0., self.number_of_displayed_imports(network_path) as f64 * 24.); - let add_import = ClickTarget::new(Subpath::new_ellipse(add_import_center - DVec2::new(8., 8.), add_import_center + DVec2::new(8., 8.)), 0.); + let offset_from_top_left = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(-4. * GRID_SIZE as f64, 0.) + }; + + let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; + let import_top_left = DVec2::new(viewport_top_left.x.min(bounding_box_top_left.x), viewport_top_left.y.min(bounding_box_top_left.y)); + let add_import_center = import_top_left + DVec2::new(0., self.number_of_displayed_imports(network_path) as f64 * 24.); + let add_import = ClickTarget::new( + Subpath::new_rounded_rect(add_import_center - DVec2::new(12., 12.), add_import_center + DVec2::new(12., 12.), [3.; 4]), + 0., + ); + add_import_export.insert_custom_output_port(0, add_import); + + let Some(import_exports) = self.import_export_ports(network_path) else { + log::error!("Could not get import_export_ports in load_modify_import_export"); + return; + }; + + for (export_index, export_click_target) in import_exports.input_ports() { + let Some(export_bounding_box) = export_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + remove_imports_exports.insert_input_port_at_center(*export_index, (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(16., 0.)); + } + + for (import_index, import_click_target) in import_exports.output_ports() { + let Some(import_bounding_box) = import_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + remove_imports_exports.insert_output_port_at_center(*import_index, (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-16., 0.)); + } + } let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get current network in load_modify_import_export"); @@ -2009,12 +2031,9 @@ impl NodeNetworkInterface { }; network_metadata.transient_metadata.modify_import_export = TransientMetadata::Loaded(ModifyImportExportClickTarget { - add_export, - add_import, - remove_imports: Vec::new(), - remove_exports: Vec::new(), - move_import: Vec::new(), - move_export: Vec::new(), + add_import_export, + remove_imports_exports, + move_imports_exports: Ports::new(), }); } @@ -2693,6 +2712,19 @@ impl NodeNetworkInterface { let mut import_exports_bounding_box = String::new(); let _ = import_exports_target.subpath_to_svg(&mut import_exports_bounding_box, DAffine2::IDENTITY); + let mut modify_import_export = Vec::new(); + if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) { + for click_target in modify_import_export_click_targets + .add_import_export + .click_targets() + .chain(modify_import_export_click_targets.remove_imports_exports.click_targets()) + .chain(modify_import_export_click_targets.move_imports_exports.click_targets()) + { + let mut remove_string = String::new(); + let _ = click_target.subpath().subpath_to_svg(&mut remove_string, DAffine2::IDENTITY); + modify_import_export.push(remove_string); + } + } FrontendClickTargets { node_click_targets, layer_click_targets, @@ -2700,6 +2732,7 @@ impl NodeNetworkInterface { icon_click_targets, all_nodes_bounding_box, import_exports_bounding_box, + modify_import_export, } } @@ -3226,10 +3259,6 @@ impl NodeNetworkInterface { log::error!("Cannot add import for document network"); return; }; - // Set the node to be a non layer if it is no longer eligible to be a layer - if !self.is_eligible_to_be_layer(&node_id, &encapsulating_network_path) && self.is_layer(&node_id, &encapsulating_network_path) { - self.set_to_node_or_layer(&node_id, &encapsulating_network_path, false); - } let Some(network) = self.network_mut(&encapsulating_network_path) else { log::error!("Could not get nested network in insert_input"); @@ -3249,6 +3278,11 @@ impl NodeNetworkInterface { self.transaction_modified(); + // Set the node to be a non layer if it is no longer eligible to be a layer + if !self.is_eligible_to_be_layer(&node_id, &encapsulating_network_path) && self.is_layer(&node_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&node_id, &encapsulating_network_path, false); + } + let Some(node_metadata) = self.node_metadata_mut(&node_id, &encapsulating_network_path) else { log::error!("Could not get node_metadata in insert_input"); return; @@ -3259,27 +3293,134 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.input_names.insert(insert_index as usize, input_name); } - // Update the internal network import ports and outwards connections (if has a network implementation) - if let Some(internal_network) = &mut node_metadata.persistent_metadata.network_metadata { - internal_network.transient_metadata.import_export_ports.unload(); - internal_network.transient_metadata.outward_wires.unload(); + // Update the metadata for the encapsulating node + self.unload_node_click_targets(&node_id, &encapsulating_network_path); + self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if encapsulating_network_path.is_empty() && (insert_index == 0 || insert_index == 1) { + self.load_structure(); } - // Update the click targets for the node - self.unload_node_click_targets(&node_id, &encapsulating_network_path); + // Unload the metadata for the nested network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); + } + + // First disconnects the export, then removes it + pub fn remove_export(&mut self, export_index: usize, network_path: &[NodeId]) { + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Cannot remove export for document network"); + return; + }; - // Update the transient network metadata bounding box for all nodes and outward wires + self.disconnect_input(&InputConnector::Export(export_index), network_path); + + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in add_export"); + return; + }; + + network.exports.remove(export_index); + + self.transaction_modified(); + + // There will not be an encapsulating node if the network is the document network + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating node metadata in remove_export"); + return; + }; + encapsulating_node_metadata.persistent_metadata.output_names.remove(export_index); + + // Update the metadata for the encapsulating node + self.unload_outward_wires(&encapsulating_network_path); + self.unload_node_click_targets(&parent_id, &encapsulating_network_path); self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if !self.is_eligible_to_be_layer(&parent_id, &encapsulating_network_path) && self.is_layer(&parent_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&parent_id, &encapsulating_network_path, false); + } + if encapsulating_network_path.is_empty() { + self.load_structure(); + } // Unload the metadata for the nested network self.unload_outward_wires(network_path); self.unload_import_export_ports(network_path); self.unload_modify_import_export(network_path); + } - // If the input is inserted as the first input, then it may have affected the document metadata structure - if encapsulating_network_path.is_empty() && (insert_index == 0 || insert_index == 1) { + // First disconnects the import, then removes it + pub fn remove_import(&mut self, import_index: usize, network_path: &[NodeId]) { + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Cannot remove export for document network"); + return; + }; + + let number_of_inputs = self.number_of_inputs(&parent_id, &encapsulating_network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in remove_import"); + return; + }; + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + let mut new_import_mapping = Vec::new(); + for i in (import_index + 1)..number_of_inputs { + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(i)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + for upstream_input_wire in outward_wires_for_import { + new_import_mapping.push((OutputConnector::Import(i - 1), upstream_input_wire)); + } + } + + // Disconnect all upstream connections + for outward_wire in outward_wires_for_import { + self.disconnect_input(&outward_wire, network_path); + } + // Shift inputs connected to to imports at a higher index down one + for (output_connector, input_wire) in new_import_mapping { + self.create_wire(&output_connector, &input_wire, network_path); + } + + let Some(network) = self.network_mut(&encapsulating_network_path) else { + log::error!("Could not get parent node in remove_import"); + return; + }; + let Some(node) = network.nodes.get_mut(&parent_id) else { + log::error!("Could not get node in remove_import"); + return; + }; + + node.inputs.remove(import_index); + + self.transaction_modified(); + + // There will not be an encapsulating node if the network is the document network + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating node metadata in remove_export"); + return; + }; + encapsulating_node_metadata.persistent_metadata.input_names.remove(import_index); + + // Update the metadata for the encapsulating node + self.unload_outward_wires(&encapsulating_network_path); + self.unload_node_click_targets(&parent_id, &encapsulating_network_path); + self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if !self.is_eligible_to_be_layer(&parent_id, &encapsulating_network_path) && self.is_layer(&parent_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&parent_id, &encapsulating_network_path, false); + } + if encapsulating_network_path.is_empty() { self.load_structure(); } + + // Unload the metadata for the nested network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts @@ -5317,14 +5458,30 @@ impl Ports { .chain(self.output_ports.iter().map(|(_, click_target)| click_target)) } - pub fn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) { + pub fn input_ports(&self) -> impl Iterator { + self.input_ports.iter() + } + + pub fn output_ports(&self) -> impl Iterator { + self.output_ports.iter() + } + + fn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) { let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.)); - self.input_ports.push((input_index, ClickTarget::new(subpath, 0.))); + self.insert_custom_input_port(input_index, ClickTarget::new(subpath, 0.)); } - pub fn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) { + fn insert_custom_input_port(&mut self, input_index: usize, click_target: ClickTarget) { + self.input_ports.push((input_index, click_target)); + } + + fn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) { let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.)); - self.output_ports.push((output_index, ClickTarget::new(subpath, 0.))); + self.insert_custom_output_port(output_index, ClickTarget::new(subpath, 0.)); + } + + fn insert_custom_output_port(&mut self, output_index: usize, click_target: ClickTarget) { + self.output_ports.push((output_index, click_target)); } fn insert_node_input(&mut self, input_index: usize, row_index: usize, node_top_left: DVec2) { @@ -5509,15 +5666,12 @@ pub struct NodeNetworkTransientMetadata { #[derive(Debug, Clone)] pub struct ModifyImportExportClickTarget { - // Plus icon that appears below all imports/exports - pub add_import: ClickTarget, - pub add_export: ClickTarget, + // Plus icon that appears below all imports/exports, except in the document network + pub add_import_export: Ports, // Subtract icon that appears when hovering over an import/export - pub remove_imports: Vec, - pub remove_exports: Vec, + pub remove_imports_exports: Ports, // Grip drag icon that appears when hovering over an import/export - pub move_import: Vec, - pub move_export: Vec, + pub move_imports_exports: Ports, } #[derive(Debug, Clone)] diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index bfa6592113..9dc214e040 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -394,6 +394,9 @@ {/each} + {#each $nodeGraph.clickTargets.modifyImportExport as pathString} + + {/each} {/if} @@ -437,15 +440,22 @@ {/if} -

{outputMetadata.name}

+
+ { + /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ + }} + /> +
+

{outputMetadata.name}

{/each} {#if $nodeGraph.addImport !== undefined}
{ /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} @@ -472,13 +482,20 @@ {/if} -

{inputMetadata.name}

+
+ { + /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ + }} + /> +
+

{inputMetadata.name}

{/each} {#if $nodeGraph.addExport !== undefined}
{ @@ -878,6 +895,10 @@ .all-nodes-bounding-box { stroke: purple; } + + .modify-import-export { + stroke: orange; + } } } @@ -917,8 +938,6 @@ } .plus { - margin-top: -4px; - margin-left: -4px; position: absolute; top: calc(var(--offset-top) * 24px); left: calc(var(--offset-left) * 24px); diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index c6fdf9167d..510e53e271 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -174,6 +174,7 @@ export type FrontendClickTargets = { readonly iconClickTargets: string[]; readonly allNodesBoundingBox: string; readonly importExportsBoundingBox: string; + readonly modifyImportExport: string[]; }; export type ContextMenuInformation = {