Fix export edge distance (#3386)

This commit is contained in:
Adam Gerhant 2025-11-17 09:22:05 -08:00 committed by GitHub
parent e751979e5c
commit 85965c8b6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 24 additions and 152 deletions

View File

@ -473,7 +473,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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<DocumentMessage, DocumentMessageContext<'_>> 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)
}

View File

@ -180,7 +180,6 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> 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<NavigationMessage, NavigationMessageContext<'_>> for Navigat
};
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() });
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
}

View File

@ -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,

View File

@ -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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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<NodeGraphMessage, NodeGraphMessageContext<'a>> 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 {

View File

@ -196,8 +196,6 @@ pub struct FrontendClickTargets {
pub icon_click_targets: Vec<String>,
#[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<String>,
}

View File

@ -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<NodeId>> {
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::<PointId>::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::<PointId>::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<Ports>,
/// Click targets for adding, removing, and moving import/export ports
pub modify_import_export: TransientMetadata<ModifyImportExportClickTarget>,
// 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<NetworkEdgeDistance>,
// Wires from the exports
pub wires: Vec<TransientMetadata<WirePathUpdate>>,
@ -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

View File

@ -33,6 +33,7 @@ impl MessageHandler<ViewportMessage, ()> for ViewportMessageHandler {
offset: Point { x, y },
size: Point { x: width, y: height },
};
responses.add(NodeGraphMessage::UpdateNodeGraphWidth);
}
ViewportMessage::RepropagateUpdate => {}
}
@ -49,7 +50,6 @@ impl MessageHandler<ViewportMessage, ()> for ViewportMessageHandler {
}
responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(DeferMessage::AfterGraphRun {
messages: vec![

View File

@ -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)}
/>

View File

@ -274,7 +274,6 @@
<path class="visibility" d={pathString} />
{/each}
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.allNodesBoundingBox} />
<path class="all-nodes-bounding-box" d={$nodeGraph.clickTargets.importExportsBoundingBox} />
{#each $nodeGraph.clickTargets.modifyImportExport as pathString}
<path class="modify-import-export" d={pathString} />
{/each}

View File

@ -168,7 +168,6 @@ export type FrontendClickTargets = {
readonly connectorClickTargets: string[];
readonly iconClickTargets: string[];
readonly allNodesBoundingBox: string;
readonly importExportsBoundingBox: string;
readonly modifyImportExport: string[];
};

View File

@ -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) {