Skip to content

Commit

Permalink
Add grid snapping to graph imports/exports; improve layer panel drag …
Browse files Browse the repository at this point in the history
…into/between insertion; better preserve graph space on reordering (GraphiteEditor#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 <[email protected]>
  • Loading branch information
adamgerhant and Keavon authored Aug 11, 2024
1 parent 60707c0 commit 193f757
Show file tree
Hide file tree
Showing 16 changed files with 492 additions and 188 deletions.
2 changes: 2 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Graph
pub const GRID_SIZE: u32 = 24;
pub const EXPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;
pub const IMPORTS_TO_EDGE_PIXEL_GAP: u32 = 120;

// Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
self.viewport_bounds = bounds;

responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO });
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
}
InputPreprocessorMessage::DoubleClick { editor_mouse_state, modifier_keys } => {
Expand Down
54 changes: 31 additions & 23 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
}
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(NodeGraphMessage::SendGraph);
}
DocumentMessage::FlipSelectedLayers { flip_axis } => {
Expand Down Expand Up @@ -443,6 +444,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// Update the tilt menu bar buttons to be disabled when the graph is open
responses.add(MenuBarMessage::SendLayout);
if open {
responses.add(NodeGraphMessage::SetGridAlignedEdges);
responses.add(NodeGraphMessage::SendGraph);
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: 0. });
}
Expand Down Expand Up @@ -585,13 +587,37 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
return;
}

let layers_to_move = self.network_interface.shallowest_unique_layers(&self.selection_network_path).collect::<Vec<_>>();
let layers_to_move = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
// 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.
let layers_to_move_with_insert_offset: Vec<(LayerNodeIdentifier, usize)> = layers_to_move
.iter()
.map(|layer| {
if layer.parent(self.metadata()) != Some(parent) {
(*layer, 0)
} else {
let upstream_selected_siblings = layer
.downstream_siblings(self.network_interface.document_metadata())
.filter(|sibling| {
sibling != layer
&& layers_to_move.iter().any(|layer| {
layer == sibling
&& layer
.parent(self.metadata())
.is_some_and(|parent| parent.children(self.metadata()).position(|child| child == *layer) < Some(insert_index))
})
})
.count();
(*layer, upstream_selected_siblings)
}
})
.collect::<Vec<_>>();

for layer_to_move in layers_to_move.into_iter().rev() {
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
let calculated_insert_index = insert_index + layer_index - insert_offset;
responses.add(NodeGraphMessage::MoveLayerToStack {
layer: layer_to_move,
parent,
insert_index,
insert_index: calculated_insert_index,
});
}

Expand All @@ -600,15 +626,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
DocumentMessage::MoveSelectedLayersToGroup { parent } => {
// Group all shallowest unique selected layers in order
let all_layers_to_group = self.network_interface.shallowest_unique_layers(&self.selection_network_path).collect::<Vec<_>>();

// Ensure nodes are grouped in the correct order
let mut all_layers_to_group_sorted = Vec::new();
for descendant in LayerNodeIdentifier::ROOT_PARENT.descendants(self.metadata()) {
if all_layers_to_group.contains(&descendant) {
all_layers_to_group_sorted.push(descendant);
};
}
let all_layers_to_group_sorted = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);

for layer_to_group in all_layers_to_group_sorted.into_iter().rev() {
responses.add(NodeGraphMessage::MoveLayerToStack {
Expand Down Expand Up @@ -1044,11 +1062,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let transform = self
.navigation_handler
.calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
self.network_interface.set_transform(
transform,
DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.),
&self.breadcrumb_network_path,
);
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();
responses.add(DocumentMessage::RenderRulers);
Expand Down Expand Up @@ -1261,12 +1275,6 @@ impl DocumentMessageHandler {
Ok(document_message_handler)
}

pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
let mut document = Self::deserialize_document(&serialized_content)?;
document.name = name;
Ok(document)
}

/// Called recursively by the entry function [`serialize_root`].
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
let mut space = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation

match message {
NavigationMessage::BeginCanvasPan => {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Grabbing });

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

self.mouse_position = ipp.mouse.position;
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
return;
};
self.navigation_operation = NavigationOperation::Pan { pan_original_for_abort: ptz.pan };
}
NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu } => {
Expand Down Expand Up @@ -172,6 +172,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
true => (-ipp.mouse.scroll_delta.y, 0.).into(),
} * 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 {
Expand All @@ -182,6 +183,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
ptz.set_zoom(1.);
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 {
Expand Down Expand Up @@ -252,6 +254,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
ptz.set_zoom(zoom);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
NavigationMessage::EndCanvasPTZ { abort_transform } => {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
Expand All @@ -272,14 +275,13 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
ptz.set_zoom(zoom_original_for_abort);
}
}

responses.add(DocumentMessage::PTZUpdate);
}

// Final chance to apply snapping if the key was pressed during this final frame
ptz.tilt = self.snapped_tilt(ptz.tilt);
ptz.set_zoom(self.snapped_zoom(ptz.zoom()));

responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
// Reset the navigation operation now that it's done
self.navigation_operation = NavigationOperation::None;

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

responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
NavigationMessage::FitViewportToSelection => {
if let Some(bounds) = selection_bounds {
Expand Down Expand Up @@ -420,6 +423,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
};

responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: ptz.zoom() });
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub enum NodeGraphMessage {
DisconnectInput {
input_connector: InputConnector,
},
DisconnectRootNode,
EnterNestedNetwork,
DuplicateSelectedNodes,
ExposeInput {
Expand Down Expand Up @@ -92,6 +93,7 @@ pub enum NodeGraphMessage {
SendClickTargets,
EndSendClickTargets,
SendGraph,
SetGridAlignedEdges,
SetInputValue {
node_id: NodeId,
input_index: usize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
NodeGraphMessage::DisconnectInput { input_connector } => {
network_interface.disconnect_input(&input_connector, selection_network_path);
}
NodeGraphMessage::DisconnectRootNode => {
network_interface.start_previewing_without_restore(selection_network_path);
}
NodeGraphMessage::DuplicateSelectedNodes => {
let all_selected_nodes = network_interface.upstream_chain_nodes(selection_network_path);

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

let Some(output_connector) = network_interface.upstream_output_connector(clicked_input, selection_network_path) else {
log::error!("Could not get upstream node from {clicked_input:?} when moving existing wire");
return;
let output_connector = if *clicked_input == InputConnector::Export(0) {
network_interface.root_node(selection_network_path).map(|root_node| root_node.to_connector())
} else {
network_interface.upstream_output_connector(clicked_input, selection_network_path)
};
let Some(output_connector) = output_connector else { return };
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
return;
}
Expand Down Expand Up @@ -553,9 +558,19 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
// Disconnect if the wire was previously connected to an input
if let Some(disconnecting) = &self.disconnecting {
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::DisconnectInput {
input_connector: disconnecting.clone(),
});
let mut disconnect_root_node = false;
if let Previewing::Yes { root_node_to_restore } = network_interface.previewing(selection_network_path) {
if root_node_to_restore.is_some() && *disconnecting == InputConnector::Export(0) {
disconnect_root_node = true;
}
}
if disconnect_root_node {
responses.add(NodeGraphMessage::DisconnectRootNode);
} else {
responses.add(NodeGraphMessage::DisconnectInput {
input_connector: disconnecting.clone(),
});
}
// Update the frontend that the node is disconnected
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph);
Expand Down Expand Up @@ -946,6 +961,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SendSelectedNodes);
}
}
NodeGraphMessage::SetGridAlignedEdges => {
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();
responses.add(FrontendMessage::UpdateImportsExports { imports, exports });
}
}
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
let input = NodeInput::value(value, false);
responses.add(NodeGraphMessage::SetInput {
Expand Down Expand Up @@ -988,9 +1012,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
continue;
}

let mut is_downstream_from_selected_absolute_layer = false;
// Deselect stack nodes upstream from a selected layer
let mut is_upstream_from_selected_absolute_layer = false;
let mut current_node = *selected_node;
loop {
if network_interface.is_absolute(selected_node, selection_network_path) {
break;
}
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
break;
};
Expand All @@ -1009,16 +1037,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if !network_interface.is_layer(&downstream_node, selection_network_path) {
break;
}
if network_interface.is_absolute(&downstream_node, selection_network_path) {
is_downstream_from_selected_absolute_layer = node_ids.contains(&downstream_node);
// Break the iteration if the layer is absolute(top of stack), or it is selected
if network_interface.is_absolute(&downstream_node, selection_network_path) || node_ids.contains(&downstream_node) {
is_upstream_from_selected_absolute_layer = node_ids.contains(&downstream_node);
break;
}
current_node = downstream_node;
}
if is_downstream_from_selected_absolute_layer {
continue;
if !is_upstream_from_selected_absolute_layer {
filtered_node_ids.push(*selected_node)
}
filtered_node_ids.push(*selected_node)
}

for node_id in filtered_node_ids {
Expand Down Expand Up @@ -1052,7 +1080,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
return;
}

network_interface.set_to_node_or_layer(&node_id, selection_network_path, is_layer);

self.context_menu = None;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,6 @@ pub struct FrontendClickTargets {
pub visibility_click_targets: Vec<String>,
#[serde(rename = "allNodesBoundingBox")]
pub all_nodes_bounding_box: String,
#[serde(rename = "importExportsBoundingBox")]
pub import_exports_bounding_box: String,
}
Original file line number Diff line number Diff line change
Expand Up @@ -284,10 +284,10 @@ impl LayerNodeIdentifier {
}
}

pub fn upstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter {
pub fn downstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
layer_node: Some(self),
next_node: Self::next_sibling,
next_node: Self::previous_sibling,
metadata,
}
}
Expand Down
Loading

0 comments on commit 193f757

Please sign in to comment.