diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 91303f8d97..3157017d8b 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -182,8 +182,6 @@ impl MessageHandler> for DocumentMes device_pixel_ratio, } = context; - let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path); - let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport(); match message { // Sub-messages DocumentMessage::Navigation(message) => { @@ -191,11 +189,6 @@ impl MessageHandler> for DocumentMes network_interface: &mut self.network_interface, breadcrumb_network_path: &self.breadcrumb_network_path, ipp, - selection_bounds: if self.graph_view_overlay_open { - selected_nodes_bounding_box_viewport - } else { - selected_visible_layers_bounding_box_viewport - }, document_ptz: &mut self.document_ptz, graph_view_overlay_open: self.graph_view_overlay_open, preferences, @@ -259,7 +252,7 @@ impl MessageHandler> for DocumentMes AlignAxis::X => DVec2::X, AlignAxis::Y => DVec2::Y, }; - let Some(combined_box) = self.selected_visible_layers_bounding_box_viewport() else { + let Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else { return; }; @@ -486,7 +479,7 @@ impl MessageHandler> for DocumentMes FlipAxis::X => DVec2::new(-1., 1.), FlipAxis::Y => DVec2::new(1., -1.), }; - if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() { + if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() { let center = (max + min) / 2.; let bbox_trans = DAffine2::from_translation(-center); let mut added_transaction = false; @@ -506,7 +499,7 @@ impl MessageHandler> for DocumentMes } DocumentMessage::RotateSelectedLayers { degrees } => { // Get the bounding box of selected layers in viewport space - if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() { + if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() { // Calculate the center of the bounding box to use as rotation pivot let center = (max + min) / 2.; // Transform that moves pivot point to origin @@ -1063,13 +1056,13 @@ impl MessageHandler> for DocumentMes self.selected_layers_reorder(relative_index_offset, responses); } DocumentMessage::ClipLayer { id } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); responses.add(DocumentMessage::AddTransaction); responses.add(GraphOperationMessage::ClipModeToggle { layer }); } DocumentMessage::SelectLayer { id, ctrl, shift } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); let mut nodes = vec![]; @@ -1266,7 +1259,7 @@ impl MessageHandler> for DocumentMes responses.add(OverlaysMessage::Draw); } DocumentMessage::ToggleLayerExpansion { id, recursive } => { - let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(id, &self.network_interface); let metadata = self.metadata(); let is_collapsed = self.collapsed.0.contains(&layer); @@ -1323,7 +1316,7 @@ impl MessageHandler> for DocumentMes self.network_interface.document_network().nodes.contains_key(node_id)) .filter_map(|(node_id, click_targets)| { self.network_interface.is_layer(&node_id, &[]).then(|| { - let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]); + let layer = LayerNodeIdentifier::new(node_id, &self.network_interface); (layer, click_targets) }) }) @@ -1708,31 +1701,6 @@ impl DocumentMessageHandler { .last() } - /// Get the combined bounding box of the click targets of the selected visible layers in viewport space - pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_layers(&self.network_interface) - .filter_map(|layer| self.metadata().bounding_box_viewport(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - - pub fn selected_visible_and_unlock_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&self.network_interface) - .filter_map(|layer| self.metadata().bounding_box_viewport(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - - pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> { - self.network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&self.network_interface) - .map(|layer| self.metadata().nonzero_bounding_box(layer)) - .reduce(graphene_std::renderer::Quad::combine_bounds) - } - pub fn document_network(&self) -> &NodeNetwork { self.network_interface.document_network() } 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 c7f6fc12cb..b7ea613323 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 @@ -119,7 +119,7 @@ impl MessageHandler> for let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone(); if let NodeInput::Node { node_id, .. } = &primary_input { if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) { - network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface, &[]), artboard_layer, 0, &[]); + network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]); } else { network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]); network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]); 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 1f6bce9e9e..85cce0c5c8 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -124,7 +124,7 @@ impl<'a> ModifyInputsContext<'a> { pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier { let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template(); self.network_interface.insert_node(new_id, new_merge_node, &[]); - LayerNodeIdentifier::new(new_id, self.network_interface, &[]) + LayerNodeIdentifier::new(new_id, self.network_interface) } /// Creates an artboard as the primary export for the document network @@ -138,7 +138,7 @@ impl<'a> ModifyInputsContext<'a> { Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)), ]); self.network_interface.insert_node(new_id, artboard_node_template, &[]); - LayerNodeIdentifier::new(new_id, self.network_interface, &[]) + LayerNodeIdentifier::new(new_id, self.network_interface) } pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) { @@ -236,7 +236,7 @@ impl<'a> ModifyInputsContext<'a> { self.layer_node.or_else(|| { let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?; if self.network_interface.is_layer(&export_node, &[]) { - Some(LayerNodeIdentifier::new(export_node, self.network_interface, &[])) + Some(LayerNodeIdentifier::new(export_node, self.network_interface)) } else { None } diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index c1128f0080..e01e1195e1 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -18,7 +18,6 @@ pub struct NavigationMessageContext<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], pub ipp: &'a InputPreprocessorMessageHandler, - pub selection_bounds: Option<[DVec2; 2]>, pub document_ptz: &'a mut PTZ, pub graph_view_overlay_open: bool, pub preferences: &'a PreferencesMessageHandler, @@ -39,7 +38,6 @@ impl MessageHandler> for Navigat network_interface, breadcrumb_network_path, ipp, - selection_bounds, document_ptz, graph_view_overlay_open, preferences, @@ -386,9 +384,16 @@ impl MessageHandler> for Navigat responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); } + // Fully zooms in on the selected NavigationMessage::FitViewportToSelection => { + let selection_bounds = if graph_view_overlay_open { + network_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path) + } else { + network_interface.selected_layers_artwork_bounding_box_viewport() + }; + if let Some(bounds) = selection_bounds { - let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { + let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { log::error!("Could not get node graph PTZ in FitViewportToSelection"); return; }; 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 930a96af67..63c8013318 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 @@ -1387,6 +1387,11 @@ impl<'a> MessageHandler> for NodeG if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y { nodes.push(*node_id); } + for error in &self.node_graph_errors { + if error.node_path.contains(node_id) { + nodes.push(*node_id); + } + } } responses.add(FrontendMessage::UpdateVisibleNodes { nodes }); @@ -2399,19 +2404,19 @@ impl NodeGraphMessageHandler { let mut ancestors_of_selected = HashSet::new(); let mut descendants_of_selected = HashSet::new(); for selected_layer in &selected_layers { - for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).ancestors(network_interface.document_metadata()) { + for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) { if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer { ancestors_of_selected.insert(ancestor.to_node()); } } - for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).descendants(network_interface.document_metadata()) { + for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) { descendants_of_selected.insert(descendant.to_node()); } } for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata { if node_metadata.persistent_metadata.is_layer() { - let layer = LayerNodeIdentifier::new(node_id, network_interface, &[]); + let layer = LayerNodeIdentifier::new(node_id, network_interface); let children_allowed = // The layer has other layers as children along the secondary input's horizontal flow diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 0b6f65bc87..600d4958f6 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -250,12 +250,8 @@ impl LayerNodeIdentifier { /// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks. #[track_caller] - pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self { - debug_assert!( - network_interface.is_layer(&node_id, network_path), - "Layer identifier constructed from non-layer node {node_id}: {:#?}", - network_interface.nested_network(network_path).unwrap().nodes.get(&node_id) - ); + pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self { + debug_assert!(network_interface.is_layer(&node_id, &[]), "Layer identifier constructed from non-layer node {node_id}",); Self::new_unchecked(node_id) } 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 4a4258a742..dd6eeb95d0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -203,12 +203,12 @@ impl NodeNetworkInterface { } /// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself. - pub fn downstream_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { + pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { let mut id = *node_id; while !self.is_layer(&id, network_path) { id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?; } - Some(LayerNodeIdentifier::new(id, self, network_path)) + Some(id) } /// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself. @@ -388,8 +388,8 @@ impl NodeNetworkInterface { } // If a chain node does not have a selected downstream layer, then set the position to absolute - let downstream_layer = self.downstream_layer(node_id, network_path); - if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) { + let downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path); + if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) { let Some(position) = self.position(node_id, network_path) else { log::error!("Could not get position in create_node_template"); return None; @@ -1244,7 +1244,7 @@ impl NodeNetworkInterface { .as_ref() .is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[])) { - Some(LayerNodeIdentifier::new(*node_id, self, &[])) + Some(LayerNodeIdentifier::new(*node_id, self)) } else { None } @@ -3025,7 +3025,7 @@ impl NodeNetworkInterface { // Helper functions for mutable getters impl NodeNetworkInterface { - pub fn upstream_chain_nodes(&mut self, network_path: &[NodeId]) -> Vec { + pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { log::error!("Could not get selected nodes in upstream_chain_nodes"); return Vec::new(); @@ -3156,7 +3156,7 @@ impl NodeNetworkInterface { self.document_metadata.document_to_viewport = transform; } - pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { let Some(node) = self.document_node(node_id, network_path) else { log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); return false; @@ -3362,6 +3362,24 @@ impl NodeNetworkInterface { .map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)]) } + pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { + self.selected_nodes() + .0 + .iter() + .filter(|node| self.is_layer(&node, &[])) + .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) + .reduce(Quad::combine_bounds) + } + + pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> { + self.selected_nodes() + .0 + .iter() + .filter(|node| self.is_layer(&node, &[]) && !self.is_layer(&node, &[])) + .filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self))) + .reduce(Quad::combine_bounds) + } + /// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> { let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else { @@ -3451,7 +3469,7 @@ impl NodeNetworkInterface { let Some(first_root_layer) = self .upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow) - .find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self, &[])) } else { None }) + .find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None }) else { return; }; @@ -3467,7 +3485,7 @@ impl NodeNetworkInterface { if horizontal_root_node_id == first_root_layer.to_node() { for current_node_id in horizontal_flow_iter { if self.is_layer(¤t_node_id, &[]) { - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { if current_node_id == first_root_layer.to_node() { awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT)); @@ -3484,7 +3502,7 @@ impl NodeNetworkInterface { // Skip the horizontal_root_node_id node for current_node_id in horizontal_flow_iter.skip(1) { if self.is_layer(¤t_node_id, &[]) { - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { awaiting_primary_flow.push((current_node_id, parent_layer_node)); children.push((parent_layer_node, current_layer_node)); @@ -3505,7 +3523,7 @@ impl NodeNetworkInterface { for current_node_id in primary_flow_iter.skip(1) { if self.is_layer(¤t_node_id, &[]) { // Create a new layer for the top of each stack, and add it as a child to the previous parent - let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]); + let current_layer_node = LayerNodeIdentifier::new(current_node_id, self); if !self.document_metadata.structure.contains_key(¤t_layer_node) { children.push(current_layer_node); @@ -3568,7 +3586,7 @@ impl NodeNetworkInterface { } stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| { - let mut current_path = path.clone(); + let mut current_path: Vec = path.clone(); current_path.push(*node_id); current_path })); @@ -5087,8 +5105,8 @@ impl NodeNetworkInterface { } self.unload_upstream_node_click_targets(vec![*node_id], network_path); // Reload click target of the layer which encapsulate the chain - if let Some(downstream_layer) = self.downstream_layer(node_id, network_path) { - self.unload_node_click_targets(&downstream_layer.to_node(), network_path); + if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) { + self.unload_node_click_targets(&downstream_layer, network_path); } self.unload_all_nodes_bounding_box(network_path); } @@ -5205,7 +5223,7 @@ impl NodeNetworkInterface { /// node_id is the first chain node, not the layer fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) { - let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else { + let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else { log::error!("Could not get downstream layer in set_upstream_chain_to_absolute"); return; }; @@ -5218,7 +5236,7 @@ impl NodeNetworkInterface { if self.is_chain(upstream_id, network_path) { self.set_absolute_position(upstream_id, previous_position, network_path); // Reload click target of the layer which used to encapsulate the chain - self.unload_node_click_targets(&downstream_layer.to_node(), network_path); + self.unload_node_click_targets(&downstream_layer, network_path); } // If there is an upstream layer then stop breaking the chain else { @@ -5297,8 +5315,8 @@ impl NodeNetworkInterface { // Deselect chain nodes upstream from a selected layer if self.is_chain(selected_node, network_path) && self - .downstream_layer(selected_node, network_path) - .is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node())) + .downstream_layer_for_chain_node(selected_node, network_path) + .is_some_and(|downstream_layer| node_ids.contains(&downstream_layer)) { node_ids.remove(selected_node); } diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index b18b37d3f6..71de86b90f 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -789,6 +789,8 @@ impl MessageHandler> for Portfolio responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open }); if node_graph_open { responses.add(NodeGraphMessage::UpdateGraphBarRight); + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendWires) } else { responses.add(PortfolioMessage::UpdateDocumentWidgets); } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index c89373f956..203c84646f 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -1323,7 +1323,7 @@ mod test_transform_layer { let document = editor.active_document_mut(); let group_children = document.network_interface.downstream_layers(&group_layer.to_node(), &[]); if !group_children.is_empty() { - Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface, &[])) + Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface)) } else { None }