From 85965c8b6ac9d37cedfb3be8054b9ab9429561e9 Mon Sep 17 00:00:00 2001 From: Adam Gerhant <116332429+adamgerhant@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:22:05 -0800 Subject: [PATCH] Fix export edge distance (#3386) --- .../document/document_message_handler.rs | 7 +- .../navigation/navigation_message_handler.rs | 6 - .../document/node_graph/node_graph_message.rs | 2 +- .../node_graph/node_graph_message_handler.rs | 14 +- .../document/node_graph/utility_types.rs | 2 - .../utility_types/network_interface.rs | 133 ++---------------- .../viewport/viewport_message_handler.rs | 2 +- .../src/components/panels/Document.svelte | 1 - frontend/src/components/views/Graph.svelte | 1 - frontend/src/messages.ts | 1 - frontend/wasm/src/editor_api.rs | 7 - 11 files changed, 24 insertions(+), 152 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index d9a69059..e86f79b2 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -473,7 +473,7 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::ZoomCanvasToFitAll); - responses.add(NodeGraphMessage::SetGridAlignedEdges); + responses.add(NodeGraphMessage::UpdateNodeGraphWidth); } DocumentMessage::Escape => { if self.node_graph_handler.drag_start.is_some() { @@ -506,7 +506,7 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::PTZUpdate); - responses.add(NodeGraphMessage::SetGridAlignedEdges); + responses.add(NodeGraphMessage::UpdateNodeGraphWidth); } DocumentMessage::FlipSelectedLayers { flip_axis } => { let scale = match flip_axis { @@ -576,10 +576,10 @@ impl MessageHandler> for DocumentMes responses.add(ToolMessage::DeactivateTools); responses.add(OverlaysMessage::Draw); // Clear the overlays responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. }); - responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(NodeGraphMessage::UpdateGraphBarRight); responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::UpdateHints); + responses.add(NodeGraphMessage::UpdateEdges); } else { responses.add(ToolMessage::ActivateTool { tool_type: *current_tool }); responses.add(OverlaysMessage::Draw); // Redraw overlays when graph is closed @@ -1972,7 +1972,6 @@ impl DocumentMessageHandler { // TODO: Remove once the footprint is used to load the imports/export distances from the edge responses.add(NodeGraphMessage::UnloadWires); - responses.add(NodeGraphMessage::SetGridAlignedEdges); Some(previous_network) } 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 ec1ce9aa..4161d6f7 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -180,7 +180,6 @@ impl MessageHandler> for Navigat NavigationMessage::CanvasPanMouseWheel { use_y_as_x } => { let delta = if use_y_as_x { (-ipp.mouse.scroll_delta.y, 0.).into() } else { -ipp.mouse.scroll_delta.as_dvec2() } * VIEWPORT_SCROLL_RATE; responses.add(NavigationMessage::CanvasPan { delta }); - responses.add(NodeGraphMessage::SetGridAlignedEdges); } NavigationMessage::CanvasTiltResetAndZoomTo100Percent => { let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { @@ -195,7 +194,6 @@ impl MessageHandler> for Navigat responses.add(PortfolioMessage::UpdateDocumentWidgets); } responses.add(DocumentMessage::PTZUpdate); - responses.add(NodeGraphMessage::SetGridAlignedEdges); } NavigationMessage::CanvasTiltSet { angle_radians } => { let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else { @@ -274,7 +272,6 @@ impl MessageHandler> for Navigat responses.add(PortfolioMessage::UpdateDocumentWidgets); } responses.add(DocumentMessage::PTZUpdate); - responses.add(NodeGraphMessage::SetGridAlignedEdges); } NavigationMessage::CanvasFlip => { if graph_view_overlay_open { @@ -322,7 +319,6 @@ impl MessageHandler> for Navigat } else { responses.add(PortfolioMessage::UpdateDocumentWidgets); } - responses.add(NodeGraphMessage::SetGridAlignedEdges); // Reset the navigation operation now that it's done self.navigation_operation = NavigationOperation::None; @@ -384,7 +380,6 @@ impl MessageHandler> for Navigat responses.add(PortfolioMessage::UpdateDocumentWidgets); } responses.add(DocumentMessage::PTZUpdate); - responses.add(NodeGraphMessage::SetGridAlignedEdges); } // Fully zooms in on the selected NavigationMessage::FitViewportToSelection => { @@ -479,7 +474,6 @@ impl MessageHandler> for Navigat }; responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() }); - responses.add(NodeGraphMessage::SetGridAlignedEdges); } } 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 196b28ec..330d123e 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 @@ -113,6 +113,7 @@ pub enum NodeGraphMessage { shift: Key, }, ShakeNode, + UpdateNodeGraphWidth, RemoveImport { import_index: usize, }, @@ -144,7 +145,6 @@ pub enum NodeGraphMessage { SendWires, UpdateVisibleNodes, SendGraph, - SetGridAlignedEdges, SetInputValue { node_id: NodeId, input_index: usize, 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 401140f9..c0ad7597 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 @@ -20,7 +20,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed; use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; -use crate::messages::viewport::{Position, Rect}; +use crate::messages::viewport::Position; use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; @@ -1543,6 +1543,10 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::SendGraph); } + NodeGraphMessage::UpdateNodeGraphWidth => { + network_interface.set_node_graph_width(viewport.size().x(), breadcrumb_network_path); + responses.add(NodeGraphMessage::UpdateImportsExports); + } NodeGraphMessage::RemoveImport { import_index: usize } => { network_interface.remove_import(usize, selection_network_path); responses.add(NodeGraphMessage::UpdateImportsExports); @@ -1659,14 +1663,6 @@ impl<'a> MessageHandler> for NodeG self.update_node_graph_hints(responses); } } - NodeGraphMessage::SetGridAlignedEdges => { - if graph_view_overlay_open { - let viewport_bounds = viewport.bounds(); - network_interface.set_grid_aligned_edges(DVec2::new(viewport_bounds.x() - viewport_bounds.width(), 0.), breadcrumb_network_path); - // Send the new edges to the frontend - responses.add(NodeGraphMessage::UpdateImportsExports); - } - } NodeGraphMessage::SetInputValue { node_id, input_index, value } => { let input = NodeInput::value(value, false); responses.add(NodeGraphMessage::SetInput { 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 c22df85c..2d03c982 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -196,8 +196,6 @@ pub struct FrontendClickTargets { pub icon_click_targets: Vec, #[serde(rename = "allNodesBoundingBox")] pub all_nodes_bounding_box: String, - #[serde(rename = "importExportsBoundingBox")] - pub import_exports_bounding_box: String, #[serde(rename = "modifyImportExport")] pub modify_import_export: Vec, } 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 4f576aab..85fb237c 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -856,19 +856,18 @@ impl NodeNetworkInterface { let import_top_left = DVec2::new(top_left_inner_bound.x.min(bounding_box_top_left.x), top_left_inner_bound.y.min(bounding_box_top_left.y)); let rounded_import_top_left = DVec2::new((import_top_left.x / 24.).round() * 24., (import_top_left.y / 24.).round() * 24.); - let viewport_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; - let target_viewport_top_right = DVec2::new( - viewport_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, - viewport_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, - ); + let viewport_width = network_metadata.persistent_metadata.navigation_metadata.node_graph_width; + + let target_viewport_top_right = DVec2::new(viewport_width - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64); // An offset from the right edge in viewport pixels let node_graph_pixel_offset_top_right = node_graph_to_viewport.inverse().transform_point2(target_viewport_top_right); // A 5x5 grid offset from the right corner - let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(viewport_top_right) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); + let node_graph_grid_space_offset_top_right = node_graph_to_viewport.inverse().transform_point2(DVec2::new(viewport_width, 0.)) + DVec2::new(-5. * GRID_SIZE as f64, 4. * GRID_SIZE as f64); - // The inner bound of the export is the highest/furthest right of the two offsets + // The inner bound of the export is the highest/furthest right of the two offsets. + // When zoomed out this keeps it a constant grid space away from the edge, but when zoomed in it prevents the exports from getting too far in let top_right_inner_bound = DVec2::new( node_graph_pixel_offset_top_right.x.max(node_graph_grid_space_offset_top_right.x), node_graph_pixel_offset_top_right.y.min(node_graph_grid_space_offset_top_right.y), @@ -2249,68 +2248,6 @@ impl NodeNetworkInterface { network_metadata.transient_metadata.modify_import_export.unload(); } - pub fn rounded_network_edge_distance(&mut self, network_path: &[NodeId]) -> Option<&NetworkEdgeDistance> { - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in rounded_network_edge_distance"); - return None; - }; - if !network_metadata.transient_metadata.rounded_network_edge_distance.is_loaded() { - self.load_rounded_network_edge_distance(network_path); - } - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in rounded_network_edge_distance"); - return None; - }; - let TransientMetadata::Loaded(rounded_network_edge_distance) = &network_metadata.transient_metadata.rounded_network_edge_distance else { - log::error!("could not load import rounded_network_edge_distance"); - return None; - }; - Some(rounded_network_edge_distance) - } - - fn load_rounded_network_edge_distance(&mut self, network_path: &[NodeId]) { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - log::error!("Could not get nested network in set_grid_aligned_edges"); - return; - }; - // When setting the edges to be grid aligned, update the pixel offset to ensure the next pan starts from the snapped import/export position - let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; - // TODO: Eventually replace node graph top right with the footprint when trying to get the network edge distance - let node_graph_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; - let target_exports_distance = node_graph_to_viewport.inverse().transform_point2(DVec2::new( - node_graph_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, - node_graph_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, - )); - - let target_imports_distance = node_graph_to_viewport - .inverse() - .transform_point2(DVec2::new(IMPORTS_TO_LEFT_EDGE_PIXEL_GAP as f64, IMPORTS_TO_TOP_EDGE_PIXEL_GAP as f64)); - - let rounded_exports_distance = DVec2::new((target_exports_distance.x / 24. + 0.5).floor() * 24., (target_exports_distance.y / 24. + 0.5).floor() * 24.); - let rounded_imports_distance = DVec2::new((target_imports_distance.x / 24. + 0.5).floor() * 24., (target_imports_distance.y / 24. + 0.5).floor() * 24.); - - let rounded_viewport_exports_distance = node_graph_to_viewport.transform_point2(rounded_exports_distance); - let rounded_viewport_imports_distance = node_graph_to_viewport.transform_point2(rounded_imports_distance); - - let network_edge_distance = NetworkEdgeDistance { - exports_to_edge_distance: rounded_viewport_exports_distance, - imports_to_edge_distance: rounded_viewport_imports_distance, - }; - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - log::error!("Could not get current network in load_export_ports"); - return; - }; - network_metadata.transient_metadata.rounded_network_edge_distance = TransientMetadata::Loaded(network_edge_distance); - } - - fn unload_rounded_network_edge_distance(&mut self, network_path: &[NodeId]) { - let Some(network_metadata) = self.network_metadata_mut(network_path) else { - log::error!("Could not get nested network_metadata in unload_export_ports"); - return; - }; - network_metadata.transient_metadata.rounded_network_edge_distance.unload(); - } - fn owned_nodes(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&HashSet> { let layer_node = self.node_metadata(node_id, network_path)?; let NodeTypePersistentMetadata::Layer(LayerPersistentMetadata { owned_nodes, .. }) = &layer_node.persistent_metadata.node_type_metadata else { @@ -3155,33 +3092,6 @@ impl NodeNetworkInterface { let rect = Subpath::::new_rect(bounds[0], bounds[1]); let all_nodes_bounding_box = rect.to_bezpath().to_svg(); - let Some(rounded_network_edge_distance) = self.rounded_network_edge_distance(network_path).cloned() else { - log::error!("Could not get rounded_network_edge_distance in collect_frontend_click_targets"); - return FrontendClickTargets::default(); - }; - let Some(network_metadata) = self.network_metadata(network_path) else { - log::error!("Could not get nested network_metadata in collect_frontend_click_targets"); - return FrontendClickTargets::default(); - }; - let import_exports_viewport_top_left = rounded_network_edge_distance.imports_to_edge_distance; - let import_exports_viewport_bottom_right = rounded_network_edge_distance.exports_to_edge_distance; - - let node_graph_top_left = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(import_exports_viewport_top_left); - let node_graph_bottom_right = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(import_exports_viewport_bottom_right); - - let import_exports_target = Subpath::::new_rect(node_graph_top_left, node_graph_bottom_right); - let import_exports_bounding_box = import_exports_target.to_bezpath().to_svg(); - 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 @@ -3200,7 +3110,6 @@ impl NodeNetworkInterface { connector_click_targets, icon_click_targets, all_nodes_bounding_box, - import_exports_bounding_box, modify_import_export, } } @@ -3654,13 +3563,12 @@ impl NodeNetworkInterface { } // This should be run whenever the pan ends, a zoom occurs, or the network is opened - pub fn set_grid_aligned_edges(&mut self, node_graph_top_right: DVec2, network_path: &[NodeId]) { + pub fn set_node_graph_width(&mut self, node_graph_width: f64, network_path: &[NodeId]) { let Some(network_metadata) = self.network_metadata_mut(network_path) else { - log::error!("Could not get nested network_metadata in set_grid_aligned_edges"); + log::error!("Could not get nested network in set_transform"); return; }; - network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right = node_graph_top_right; - self.unload_rounded_network_edge_distance(network_path); + network_metadata.persistent_metadata.navigation_metadata.node_graph_width = node_graph_width; self.unload_import_export_ports(network_path); self.unload_modify_import_export(network_path); } @@ -6463,8 +6371,6 @@ pub struct NodeNetworkTransientMetadata { pub import_export_ports: TransientMetadata, /// Click targets for adding, removing, and moving import/export ports pub modify_import_export: TransientMetadata, - // Distance to the edges of the network, where the import/export ports are displayed. Rounded to nearest grid space when the panning ends. - pub rounded_network_edge_distance: TransientMetadata, // Wires from the exports pub wires: Vec>, @@ -6863,29 +6769,18 @@ pub enum LayerClickTargetTypes { // Preview, } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct NavigationMetadata { /// The current pan, and zoom state of the viewport's view of the node graph. /// Ensure `DocumentMessage::UpdateDocumentTransform` is called when the pan, zoom, or transform changes. pub node_graph_ptz: PTZ, - // TODO: Remove and replace with calculate_offset_transform from the node_graph_ptz. This will be difficult since it requires both the navigation message handler and the IPP + // TODO: Eventually remove once te click targets are extracted from the native render /// Transform from node graph space to viewport space. pub node_graph_to_viewport: DAffine2, - /// Top right of the node graph in viewport space + // TODO: Eventually remove once the import/export positions are extracted from the native render + /// The width of the node graph in viewport space #[serde(default)] - pub node_graph_top_right: DVec2, -} - -impl Default for NavigationMetadata { - fn default() -> NavigationMetadata { - // Default PTZ and transform - NavigationMetadata { - node_graph_ptz: PTZ::default(), - node_graph_to_viewport: DAffine2::IDENTITY, - // TODO: Eventually replace with footprint - node_graph_top_right: DVec2::ZERO, - } - } + pub node_graph_width: f64, } // PartialEq required by message handlers diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs index 91014965..628343bd 100644 --- a/editor/src/messages/viewport/viewport_message_handler.rs +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -33,6 +33,7 @@ impl MessageHandler for ViewportMessageHandler { offset: Point { x, y }, size: Point { x: width, y: height }, }; + responses.add(NodeGraphMessage::UpdateNodeGraphWidth); } ViewportMessage::RepropagateUpdate => {} } @@ -49,7 +50,6 @@ impl MessageHandler for ViewportMessageHandler { } responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); - responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(DeferMessage::AfterGraphRun { messages: vec![ diff --git a/frontend/src/components/panels/Document.svelte b/frontend/src/components/panels/Document.svelte index e6e5282d..3f2a96ff 100644 --- a/frontend/src/components/panels/Document.svelte +++ b/frontend/src/components/panels/Document.svelte @@ -592,7 +592,6 @@ thumbPosition={scrollbarPos.x} on:trackShift={({ detail }) => editor.handle.panCanvasByFraction(detail, 0)} on:thumbPosition={({ detail }) => panCanvasX(detail)} - on:thumbDragEnd={() => editor.handle.setGridAlignedEdges()} on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)} on:thumbDragAbort={() => editor.handle.panCanvasAbort(true)} /> diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 7ac2d4f1..468d7f3f 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -274,7 +274,6 @@ {/each} - {#each $nodeGraph.clickTargets.modifyImportExport as pathString} {/each} diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 0661e1f9..4606d2c1 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -168,7 +168,6 @@ export type FrontendClickTargets = { readonly connectorClickTargets: string[]; readonly iconClickTargets: string[]; readonly allNodesBoundingBox: string; - readonly importExportsBoundingBox: string; readonly modifyImportExport: string[]; }; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index e66f43ac..afa96ee7 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -791,13 +791,6 @@ impl EditorHandle { self.dispatch(message); } - /// Snaps the import/export edges to a grid space when the scroll bar is released - #[wasm_bindgen(js_name = setGridAlignedEdges)] - pub fn set_grid_aligned_edges(&self) { - let message = NodeGraphMessage::SetGridAlignedEdges; - self.dispatch(message); - } - /// Merge the selected nodes into a subnetwork #[wasm_bindgen(js_name = mergeSelectedNodes)] pub fn merge_nodes(&self) {