Skip to content

Commit 193f757

Browse files
adamgerhantKeavon
andauthored
Add grid snapping to graph imports/exports; improve layer panel drag into/between insertion; better preserve graph space on reordering (#1911)
* Fix disconnecting root node when previewing * Final previewing fixes * Improve positioning when moving layer to stack * Improve layer panel * Import/Export edge grid snapping * Fix layer ordering and positioning * Small bug fixes and improvements * Fix copy and paste position * Nit * Align imports/edges when using scrolling * Fix misaligned exports in demo artwork --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 60707c0 commit 193f757

File tree

16 files changed

+492
-188
lines changed

16 files changed

+492
-188
lines changed

editor/src/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Graph
22
pub const GRID_SIZE: u32 = 24;
3+
pub const EXPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;
4+
pub const IMPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;
35

46
// Viewport
57
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;

editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
3131
self.viewport_bounds = bounds;
3232

3333
responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
34+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
3435
}
3536
}
3637
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
415415
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
416416
}
417417
responses.add(DocumentMessage::PTZUpdate);
418+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
418419
responses.add(NodeGraphMessage::SendGraph);
419420
}
420421
DocumentMessage::FlipSelectedLayers { flip_axis } => {
@@ -443,6 +444,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
443444
// Update the tilt menu bar buttons to be disabled when the graph is open
444445
responses.add(MenuBarMessage::SendLayout);
445446
if open {
447+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
446448
responses.add(NodeGraphMessage::SendGraph);
447449
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });
448450
}
@@ -585,13 +587,37 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
585587
return;
586588
}
587589

588-
let layers_to_move = self.network_interface.shallowest_unique_layers(&self.selection_network_path).collect::<Vec<_>>();
590+
let layers_to_move = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
591+
// Offset the index for layers to move that are below another layer to move. For example when moving 1 and 2 between 3 and 4, 2 should be inserted at the same index as 1 since 1 is moved first.
592+
let layers_to_move_with_insert_offset: Vec<(LayerNodeIdentifier, usize)> = layers_to_move
593+
.iter()
594+
.map(|layer| {
595+
if layer.parent(self.metadata()) != Some(parent) {
596+
(*layer, 0)
597+
} else {
598+
let upstream_selected_siblings = layer
599+
.downstream_siblings(self.network_interface.document_metadata())
600+
.filter(|sibling| {
601+
sibling != layer
602+
&& layers_to_move.iter().any(|layer| {
603+
layer == sibling
604+
&& layer
605+
.parent(self.metadata())
606+
.is_some_and(|parent| parent.children(self.metadata()).position(|child| child == *layer) < Some(insert_index))
607+
})
608+
})
609+
.count();
610+
(*layer, upstream_selected_siblings)
611+
}
612+
})
613+
.collect::<Vec<_>>();
589614

590-
for layer_to_move in layers_to_move.into_iter().rev() {
615+
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
616+
let calculated_insert_index = insert_index + layer_index - insert_offset;
591617
responses.add(NodeGraphMessage::MoveLayerToStack {
592618
layer: layer_to_move,
593619
parent,
594-
insert_index,
620+
insert_index: calculated_insert_index,
595621
});
596622
}
597623

@@ -600,15 +626,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
600626
}
601627
DocumentMessage::MoveSelectedLayersToGroup { parent } => {
602628
// Group all shallowest unique selected layers in order
603-
let all_layers_to_group = self.network_interface.shallowest_unique_layers(&self.selection_network_path).collect::<Vec<_>>();
604-
605-
// Ensure nodes are grouped in the correct order
606-
let mut all_layers_to_group_sorted = Vec::new();
607-
for descendant in LayerNodeIdentifier::ROOT_PARENT.descendants(self.metadata()) {
608-
if all_layers_to_group.contains(&descendant) {
609-
all_layers_to_group_sorted.push(descendant);
610-
};
611-
}
629+
let all_layers_to_group_sorted = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
612630

613631
for layer_to_group in all_layers_to_group_sorted.into_iter().rev() {
614632
responses.add(NodeGraphMessage::MoveLayerToStack {
@@ -1044,11 +1062,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
10441062
let transform = self
10451063
.navigation_handler
10461064
.calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
1047-
self.network_interface.set_transform(
1048-
transform,
1049-
DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.),
1050-
&self.breadcrumb_network_path,
1051-
);
1065+
self.network_interface.set_transform(transform, &self.breadcrumb_network_path);
10521066
let imports = self.network_interface.frontend_imports(&self.breadcrumb_network_path).unwrap_or_default();
10531067
let exports = self.network_interface.frontend_exports(&self.breadcrumb_network_path).unwrap_or_default();
10541068
responses.add(DocumentMessage::RenderRulers);
@@ -1261,12 +1275,6 @@ impl DocumentMessageHandler {
12611275
Ok(document_message_handler)
12621276
}
12631277

1264-
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
1265-
let mut document = Self::deserialize_document(&serialized_content)?;
1266-
document.name = name;
1267-
Ok(document)
1268-
}
1269-
12701278
/// Called recursively by the entry function [`serialize_root`].
12711279
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
12721280
let mut space = 0;

editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,16 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
7171

7272
match message {
7373
NavigationMessage::BeginCanvasPan => {
74-
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
75-
return;
76-
};
7774
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });
7875

7976
responses.add(FrontendMessage::UpdateInputHints {
8077
hint_data: HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
8178
});
8279

8380
self.mouse_position = ipp.mouse.position;
81+
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
82+
return;
83+
};
8484
self.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan };
8585
}
8686
NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => {
@@ -172,6 +172,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
172172
true => (-ipp.mouse.scroll_delta.y, 0.).into(),
173173
} * VIEWPORT_SCROLL_RATE;
174174
responses.add(NavigationMessage::CanvasPan { delta });
175+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
175176
}
176177
NavigationMessage::CanvasTiltResetAndZoomTo100Percent => {
177178
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
@@ -182,6 +183,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
182183
ptz.set_zoom(1.);
183184
responses.add(PortfolioMessage::UpdateDocumentWidgets);
184185
responses.add(DocumentMessage::PTZUpdate);
186+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
185187
}
186188
NavigationMessage::CanvasTiltSet { angle_radians } => {
187189
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
@@ -252,6 +254,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
252254
ptz.set_zoom(zoom);
253255
responses.add(PortfolioMessage::UpdateDocumentWidgets);
254256
responses.add(DocumentMessage::PTZUpdate);
257+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
255258
}
256259
NavigationMessage::EndCanvasPTZ { abort_transform } => {
257260
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
@@ -272,14 +275,13 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
272275
ptz.set_zoom(zoom_original_for_abort);
273276
}
274277
}
275-
276-
responses.add(DocumentMessage::PTZUpdate);
277278
}
278279

279280
// Final chance to apply snapping if the key was pressed during this final frame
280281
ptz.tilt = self.snapped_tilt(ptz.tilt);
281282
ptz.set_zoom(self.snapped_zoom(ptz.zoom()));
282-
283+
responses.add(DocumentMessage::PTZUpdate);
284+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
283285
// Reset the navigation operation now that it's done
284286
self.navigation_operation = NavigationOperation::None;
285287

@@ -336,6 +338,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
336338

337339
responses.add(PortfolioMessage::UpdateDocumentWidgets);
338340
responses.add(DocumentMessage::PTZUpdate);
341+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
339342
}
340343
NavigationMessage::FitViewportToSelection => {
341344
if let Some(bounds) = selection_bounds {
@@ -420,6 +423,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
420423
};
421424

422425
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() });
426+
responses.add(NodeGraphMessage::SetGridAlignedEdges);
423427
}
424428
}
425429

editor/src/messages/portfolio/document/node_graph/node_graph_message.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub enum NodeGraphMessage {
4040
DisconnectInput {
4141
input_connector: InputConnector,
4242
},
43+
DisconnectRootNode,
4344
EnterNestedNetwork,
4445
DuplicateSelectedNodes,
4546
ExposeInput {
@@ -92,6 +93,7 @@ pub enum NodeGraphMessage {
9293
SendClickTargets,
9394
EndSendClickTargets,
9495
SendGraph,
96+
SetGridAlignedEdges,
9597
SetInputValue {
9698
node_id: NodeId,
9799
input_index: usize,

editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
226226
NodeGraphMessage::DisconnectInput { input_connector } => {
227227
network_interface.disconnect_input(&input_connector, selection_network_path);
228228
}
229+
NodeGraphMessage::DisconnectRootNode => {
230+
network_interface.start_previewing_without_restore(selection_network_path);
231+
}
229232
NodeGraphMessage::DuplicateSelectedNodes => {
230233
let all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);
231234

@@ -435,10 +438,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
435438
self.initial_disconnecting = true;
436439
self.disconnecting = Some(clicked_input.clone());
437440

438-
let Some(output_connector) = network_interface.upstream_output_connector(clicked_input, selection_network_path) else {
439-
log::error!("Could not get upstream node from {clicked_input:?} when moving existing wire");
440-
return;
441+
let output_connector = if *clicked_input == InputConnector::Export(0) {
442+
network_interface.root_node(selection_network_path).map(|root_node| root_node.to_connector())
443+
} else {
444+
network_interface.upstream_output_connector(clicked_input, selection_network_path)
441445
};
446+
let Some(output_connector) = output_connector else { return };
442447
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
443448
return;
444449
}
@@ -553,9 +558,19 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
553558
// Disconnect if the wire was previously connected to an input
554559
if let Some(disconnecting) = &self.disconnecting {
555560
responses.add(DocumentMessage::StartTransaction);
556-
responses.add(NodeGraphMessage::DisconnectInput {
557-
input_connector: disconnecting.clone(),
558-
});
561+
let mut disconnect_root_node = false;
562+
if let Previewing::Yes { root_node_to_restore } = network_interface.previewing(selection_network_path) {
563+
if root_node_to_restore.is_some() && *disconnecting == InputConnector::Export(0) {
564+
disconnect_root_node = true;
565+
}
566+
}
567+
if disconnect_root_node {
568+
responses.add(NodeGraphMessage::DisconnectRootNode);
569+
} else {
570+
responses.add(NodeGraphMessage::DisconnectInput {
571+
input_connector: disconnecting.clone(),
572+
});
573+
}
559574
// Update the frontend that the node is disconnected
560575
responses.add(NodeGraphMessage::RunDocumentGraph);
561576
responses.add(NodeGraphMessage::SendGraph);
@@ -946,6 +961,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
946961
responses.add(NodeGraphMessage::SendSelectedNodes);
947962
}
948963
}
964+
NodeGraphMessage::SetGridAlignedEdges => {
965+
if graph_view_overlay_open {
966+
network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path);
967+
// Send the new edges to the frontend
968+
let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default();
969+
let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default();
970+
responses.add(FrontendMessage::UpdateImportsExports { imports, exports });
971+
}
972+
}
949973
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
950974
let input = NodeInput::value(value, false);
951975
responses.add(NodeGraphMessage::SetInput {
@@ -988,9 +1012,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
9881012
continue;
9891013
}
9901014

991-
let mut is_downstream_from_selected_absolute_layer = false;
1015+
// Deselect stack nodes upstream from a selected layer
1016+
let mut is_upstream_from_selected_absolute_layer = false;
9921017
let mut current_node = *selected_node;
9931018
loop {
1019+
if network_interface.is_absolute(selected_node, selection_network_path) {
1020+
break;
1021+
}
9941022
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
9951023
break;
9961024
};
@@ -1009,16 +1037,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
10091037
if !network_interface.is_layer(&downstream_node, selection_network_path) {
10101038
break;
10111039
}
1012-
if network_interface.is_absolute(&downstream_node, selection_network_path) {
1013-
is_downstream_from_selected_absolute_layer = node_ids.contains(&downstream_node);
1040+
// Break the iteration if the layer is absolute(top of stack), or it is selected
1041+
if network_interface.is_absolute(&downstream_node, selection_network_path) || node_ids.contains(&downstream_node) {
1042+
is_upstream_from_selected_absolute_layer = node_ids.contains(&downstream_node);
10141043
break;
10151044
}
10161045
current_node = downstream_node;
10171046
}
1018-
if is_downstream_from_selected_absolute_layer {
1019-
continue;
1047+
if !is_upstream_from_selected_absolute_layer {
1048+
filtered_node_ids.push(*selected_node)
10201049
}
1021-
filtered_node_ids.push(*selected_node)
10221050
}
10231051

10241052
for node_id in filtered_node_ids {
@@ -1052,7 +1080,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
10521080
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
10531081
return;
10541082
}
1055-
10561083
network_interface.set_to_node_or_layer(&node_id, selection_network_path, is_layer);
10571084

10581085
self.context_menu = None;

editor/src/messages/portfolio/document/node_graph/utility_types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,6 @@ pub struct FrontendClickTargets {
178178
pub visibility_click_targets: Vec<String>,
179179
#[serde(rename = "allNodesBoundingBox")]
180180
pub all_nodes_bounding_box: String,
181+
#[serde(rename = "importExportsBoundingBox")]
182+
pub import_exports_bounding_box: String,
181183
}

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,10 @@ impl LayerNodeIdentifier {
284284
}
285285
}
286286

287-
pub fn upstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter {
287+
pub fn downstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter {
288288
AxisIter {
289289
layer_node: Some(self),
290-
next_node: Self::next_sibling,
290+
next_node: Self::previous_sibling,
291291
metadata,
292292
}
293293
}

0 commit comments

Comments
 (0)