Code cleanup and refactor for generalized layers (#1738)

* Move functions to messages to fix undo bugs for UnGroup and Group

* Copy+Paste for generalized layer nodes

* Fix MoveSelectedLayersTo and GroupSelectedLayers by extracting functions into messages

* Fix tests, replace FrontendMessage:TriggerPaste with PortfolioMessage::PasteIntoFolder

* Formatting

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-05-04 22:11:48 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 8d83fa7079
commit 4c3856833b
15 changed files with 507 additions and 440 deletions
@@ -1,3 +1,4 @@
use super::utility_types::clipboards::Clipboard;
use super::utility_types::error::EditorError; use super::utility_types::error::EditorError;
use super::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, OptionBoundsSnapping, OptionPointSnapping, SnappingOptions, SnappingState}; use super::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, OptionBoundsSnapping, OptionPointSnapping, SnappingOptions, SnappingState};
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
@@ -5,9 +6,7 @@ use crate::application::{generate_uuid, GRAPHITE_GIT_COMMIT_HASH};
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL}; use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData; use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options}; use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData; use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
@@ -279,36 +278,16 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
} }
DocumentMessage::DuplicateSelectedLayers => { DocumentMessage::DuplicateSelectedLayers => {
self.backup(responses); let parent = self.new_layer_parent(false);
for layer_ancestors in self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)) { let calculated_insert_index = self.get_calculated_insert_index(parent);
let Some(layer) = layer_ancestors.last().copied() else { continue };
let Some(parent) = layer.parent(&self.metadata) else { continue };
let Some(node) = self.network().nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else {
continue;
};
let nodes = NodeGraphMessageHandler::copy_nodes( responses.add(DocumentMessage::StartTransaction);
self.network(), responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
&self responses.add(PortfolioMessage::PasteIntoFolder {
.network() clipboard: Clipboard::Internal,
.upstream_flow_back_from_nodes(vec![node], FlowType::UpstreamFlow) parent,
.enumerate() insert_index: calculated_insert_index,
.map(|(index, (_, node_id))| (node_id, NodeId(index as u64))) });
.collect(),
)
.collect();
let id = NodeId(generate_uuid());
let selected_layer_index = parent.children(self.metadata()).collect::<Vec<_>>().iter().position(|&sibling| sibling == layer).unwrap_or(0);
let insert_index = if (selected_layer_index as i64 - 1) < 0 { -1 } else { selected_layer_index as isize };
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes,
parent,
insert_index,
alias: String::new(),
});
}
} }
DocumentMessage::FlipSelectedLayers { flip_axis } => { DocumentMessage::FlipSelectedLayers { flip_axis } => {
self.backup(responses); self.backup(responses);
@@ -356,6 +335,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
DocumentMessage::GroupSelectedLayers => { DocumentMessage::GroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction);
let parent = self let parent = self
.metadata() .metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false) .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false)
@@ -390,8 +371,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
continue; continue;
} }
// Disconnect above and below the old layer location responses.add(NodeGraphMessage::DisconnectLayerFromStack {
self.disconnect_node(layer, responses); node_id: layer.to_node(),
reconnect_to_sibling: true,
});
// Move disconnected node to folder // Move disconnected node to folder
let folder_position = self let folder_position = self
@@ -401,118 +384,48 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.expect("Current folder should always exist") .expect("Current folder should always exist")
.metadata .metadata
.position; .position;
responses.add(NodeGraphMessage::SetNodePosition {
let Some(layer_to_move_node_mut) = self.network.nodes.get_mut(&layer.to_node()) else { node_id: layer.to_node(),
return; position: folder_position,
}; });
DocumentMessageHandler::disconnect_input(layer_to_move_node_mut, 0);
layer_to_move_node_mut.metadata.position = folder_position;
// Insert node right above the folder // Insert node right above the folder
// TODO: Use insert layer between message
let Some((folder_downstream_node_id, folder_downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder) let Some((folder_downstream_node_id, folder_downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder)
else { else {
log::error!("Downstream node should always exist when inserting layer"); log::error!("Downstream node should always exist when inserting layer");
return; return;
}; };
let downstream_input = self responses.add(GraphOperationMessage::InsertNodeBetween {
.network post_node_id: folder_downstream_node_id,
.nodes post_node_input_index: folder_downstream_input_index,
.get_mut(&folder_downstream_node_id) insert_node_output_index: 0,
.and_then(|node| node.inputs.get_mut(folder_downstream_input_index)); insert_node_id: layer.to_node(),
let Some(NodeInput::Node { node_id, .. }) = downstream_input else { insert_node_input_index: 0,
log::error!("Downstream node should have a node input"); pre_node_output_index: 0,
return; pre_node_id: first_unselected_parent_folder.to_node(),
}; });
*node_id = layer.to_node();
// Connect layer primary input to parent folder responses.add(NodeGraphMessage::ShiftUpstream {
let Some(layer_node_input) = self.network.nodes.get_mut(&layer.to_node()).and_then(|node| node.inputs.get_mut(0)) else { node_id: first_unselected_parent_folder.to_node(),
log::error!("Layer should always have primary input"); shift: IVec2::new(0, 3),
return; shift_self: true,
}; });
*layer_node_input = NodeInput::node(first_unselected_parent_folder.to_node(), 0);
let upstream_shift = IVec2::new(0, 3);
let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses);
modify_inputs.shift_upstream(first_unselected_parent_folder.to_node(), upstream_shift, true);
} }
let calculated_insert_index = parent.children(self.metadata()).enumerate().find_map(|(index, direct_child)| { let calculated_insert_index = self.get_calculated_insert_index(parent);
if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == direct_child) {
return Some(index as isize);
}
for descendant in direct_child.descendants(self.metadata()) {
if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == descendant) {
return Some(index as isize);
}
}
None
});
let folder_id = NodeId(generate_uuid()); let folder_id = NodeId(generate_uuid());
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::NewCustomLayer { responses.add(GraphOperationMessage::NewCustomLayer {
id: folder_id, id: folder_id,
nodes: HashMap::new(), nodes: HashMap::new(),
parent, parent,
insert_index: calculated_insert_index.unwrap_or(-1), insert_index: calculated_insert_index,
alias: String::new(), alias: String::new(),
}); });
// Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer responses.add(GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent: folder_id });
let mut nodes_to_move = Vec::new();
// Skip over horizontal non layer node chain that feeds into parent
let Some(mut current_stack_node_id) = parent.first_child(&self.metadata).and_then(|current_stack_node| Some(current_stack_node.to_node())) else {
log::error!("Folder should always have child");
return;
};
let current_stack_node_id = &mut current_stack_node_id;
loop {
let mut current_stack_node = self.network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node");
// Check if the current stack node is a selected layer
if self
.selected_nodes
.selected_layers(&self.metadata)
.any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id)
{
nodes_to_move.push(*current_stack_node_id);
// Push all non layer sibling nodes directly upstream of the selected layer
loop {
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break };
let next_node = self.network.nodes.get(node_id).expect("Stack node id should always be a node");
// If the next node is a layer, immediately break and leave current stack node as the non layer node
if next_node.is_layer {
break;
}
*current_stack_node_id = *node_id;
current_stack_node = next_node;
nodes_to_move.push(*current_stack_node_id);
}
}
// Get next node
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break };
*current_stack_node_id = *node_id;
}
responses.add(GraphOperationMessage::MoveUpstreamSiblingsToChild {
new_parent: folder_id,
upstream_sibling_ids: nodes_to_move,
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
@@ -576,74 +489,23 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let binding = self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata)); let binding = self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata));
let get_last_elements = binding.iter().map(|x| x.last().expect("empty path")).collect::<Vec<_>>(); let get_last_elements = binding.iter().map(|x| x.last().expect("empty path")).collect::<Vec<_>>();
let mut run_document_graph_after = false;
// TODO: The `.collect()` is necessary to avoid borrowing issues with `self`. See if this can be avoided to improve performance. // TODO: The `.collect()` is necessary to avoid borrowing issues with `self`. See if this can be avoided to improve performance.
let ordered_last_elements = self.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).rev().collect::<Vec<_>>(); let ordered_last_elements = self.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).rev().collect::<Vec<_>>();
for layer_to_move in ordered_last_elements { for layer_to_move in ordered_last_elements {
if !run_document_graph_after && self.network.connected_to_output(layer_to_move.to_node()) { // Disconnect layer to move and reconnect downstream node to upstream sibling if it exists.
run_document_graph_after = true; responses.add(NodeGraphMessage::DisconnectLayerFromStack {
} node_id: layer_to_move.to_node(),
reconnect_to_sibling: true,
// Part 1: Disconnect layer to move and reconnect downstream node to upstream sibling if it exists. });
self.disconnect_node(layer_to_move, responses); // Reconnect layer_to_move to new parent at insert index.
responses.add(GraphOperationMessage::InsertLayerAtStackIndex {
// Part 2: Reconnect layer_to_move to new parent at insert index. layer_id: layer_to_move.to_node(),
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&self.network, parent.to_node(), insert_index); parent: parent.to_node(),
insert_index,
// Layer_to_move should always correspond to a node. });
let Some(layer_to_move_node) = self.network.nodes.get(&layer_to_move.to_node()) else {
continue;
};
// Move current layer to post node.
let post_node = self.network.nodes.get(&post_node_id).expect("Post node id should always refer to a node");
let current_position = layer_to_move_node.metadata.position;
let new_position = post_node.metadata.position;
// If moved to top of a layer stack, move to the left of the post node. The stack will be shifted down later.
// If moved within a stack, move directly on the post node. The rest of the stack will be shifted down later.
let offset_to_post_node = if insert_index == 0 {
new_position - current_position - IVec2::new(8, 0)
} else {
new_position - current_position
};
let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses);
modify_inputs.shift_upstream(layer_to_move.to_node(), offset_to_post_node, true);
// Update post_node input to layer_to_move.
// TODO: Use insert layer between message
let post_node_mut = self.network.nodes.get_mut(&post_node_id).expect("Post node id should always refer to a node");
if let Some(NodeInput::Node { node_id, .. }) = post_node_mut.inputs.get_mut(post_node_input_index) {
*node_id = layer_to_move.to_node();
} else if let Some(node_input) = post_node_mut.inputs.get_mut(post_node_input_index) {
*node_input = NodeInput::node(layer_to_move.to_node(), 0);
}
let Some(layer_to_move_node_mut) = self.network.nodes.get_mut(&layer_to_move.to_node()) else {
continue;
};
if let Some(pre_node_id) = pre_node_id {
// If pre node exists, connect layer_to_move sibling input to that node.
if let Some(node_input) = layer_to_move_node_mut.inputs.get_mut(0) {
*node_input = NodeInput::node(pre_node_id, 0);
}
}
// shift stack down, starting at the moved node.
let mut modify_inputs: ModifyInputsContext = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses);
let shift = IVec2::new(0, 3);
modify_inputs.shift_upstream(layer_to_move.to_node(), shift, true);
self.metadata.load_structure(&self.network, &mut self.selected_nodes);
} }
if run_document_graph_after { responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
DocumentMessage::NudgeSelectedLayers { DocumentMessage::NudgeSelectedLayers {
@@ -736,7 +598,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let image_frame = ImageFrame { image, ..Default::default() }; let image_frame = ImageFrame { image, ..Default::default() };
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
let layer = graph_modification_utils::new_image_layer(image_frame, NodeId(generate_uuid()), self.new_layer_parent(), responses); let layer = graph_modification_utils::new_image_layer(image_frame, NodeId(generate_uuid()), self.new_layer_parent(true), responses);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
@@ -754,7 +616,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils;
let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into()); let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into());
let center_in_viewport = DAffine2::from_translation(self.metadata().document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left)); let center_in_viewport = DAffine2::from_translation(self.metadata().document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left));
let layer = graph_modification_utils::new_svg_layer(svg, center_in_viewport, NodeId(generate_uuid()), self.new_layer_parent(), responses); let layer = graph_modification_utils::new_svg_layer(svg, center_in_viewport, NodeId(generate_uuid()), self.new_layer_parent(true), responses);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select }); responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
} }
@@ -994,14 +856,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::UngroupSelectedLayers => { DocumentMessage::UngroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let mut run_document_graph_after = false;
let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata())); let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata()));
for folder in folder_paths { for folder in folder_paths {
if !run_document_graph_after && self.network.connected_to_output(folder.to_node()) {
run_document_graph_after = true;
}
// Cannot ungroup artboard // Cannot ungroup artboard
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist"); let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
if folder_node.is_artboard() { if folder_node.is_artboard() {
@@ -1017,30 +873,29 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// Move child_layer stack x position to folder stack // Move child_layer stack x position to folder stack
let child_layer_node = self.network.nodes.get(&child_layer_node_id).expect("Child node should always exist for layer"); let child_layer_node = self.network.nodes.get(&child_layer_node_id).expect("Child node should always exist for layer");
let offset = folder_node.metadata.position - child_layer_node.metadata.position; let offset = folder_node.metadata.position - child_layer_node.metadata.position;
let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); responses.add(NodeGraphMessage::ShiftUpstream {
modify_inputs.shift_upstream(child_layer_node_id, offset, true); node_id: child_layer_node_id,
shift: offset,
shift_self: true,
});
// Set the input for the node downstream of folder to the first layer node // Set the primary input for the node downstream of folder to the first layer node
let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, folder) else { let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, folder) else {
log::error!("Downstream node should always exist when moving layer"); log::error!("Downstream node should always exist when moving layer");
continue; continue;
}; };
let Some(NodeInput::Node { node_id, .. }) = self
.network // Output_index must be 0 since layers only have 1 output
.nodes let downstream_input = NodeInput::node(child_layer_node_id, 0);
.get_mut(&downstream_node_id) responses.add(NodeGraphMessage::SetNodeInput {
.expect("downstream node should always exist") node_id: downstream_node_id,
.inputs input_index: downstream_input_index,
.get_mut(downstream_input_index) input: downstream_input,
else { });
log::error!("Could not get downstream node input");
continue;
};
*node_id = child_layer_node_id;
// Get the node that feeds into the primary input for the folder (if it exists) // Get the node that feeds into the primary input for the folder (if it exists)
if let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&folder.to_node()).expect("Folder should always exist").inputs.get(0) { if let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&folder.to_node()).expect("Folder should always exist").inputs.get(0) {
let upstream_sibling_id = *node_id; let layer_upstream_sibling_id = *node_id;
// Get the node at the bottom of the first layer node stack // Get the node at the bottom of the first layer node stack
let mut last_child_node_id = child_layer_node_id; let mut last_child_node_id = child_layer_node_id;
@@ -1051,12 +906,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
last_child_node_id = *node_id; last_child_node_id = *node_id;
} }
// Connect the primary input of the bottom layer to the node to the upstream sibling // Connect the primary input of the bottom layer of the node to the upstream sibling
let Some(node_input) = self.network.nodes.get_mut(&last_child_node_id).expect("Last child node should always exist").inputs.get_mut(0) else { let bottom_layer_node_input = NodeInput::node(layer_upstream_sibling_id, 0);
log::error!("Could not get last child node primary input"); responses.add(NodeGraphMessage::SetNodeInput {
continue; node_id: last_child_node_id,
}; input_index: 0,
*node_input = NodeInput::node(upstream_sibling_id, 0); input: bottom_layer_node_input,
});
// Shift upstream_sibling down by the height of the child layer stack // Shift upstream_sibling down by the height of the child layer stack
let top_of_stack = self.network.nodes.get(&child_layer_node_id).expect("Child layer should always exist for child layer id"); let top_of_stack = self.network.nodes.get(&child_layer_node_id).expect("Child layer should always exist for child layer id");
@@ -1064,24 +920,25 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let target_distance = bottom_of_stack.metadata.position.y - top_of_stack.metadata.position.y; let target_distance = bottom_of_stack.metadata.position.y - top_of_stack.metadata.position.y;
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist"); let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
let upstream_sibling_node = self.network.nodes.get(&upstream_sibling_id).expect("Upstream sibling node should always exist"); let upstream_sibling_node = self.network.nodes.get(&layer_upstream_sibling_id).expect("Upstream sibling node should always exist");
let current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y; let current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y;
let y_offset = target_distance - current_distance + 3; let y_offset = target_distance - current_distance + 3;
let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses); responses.add(NodeGraphMessage::ShiftUpstream {
modify_inputs.shift_upstream(upstream_sibling_id, IVec2::new(0, y_offset), true); node_id: layer_upstream_sibling_id,
shift: IVec2::new(0, y_offset),
shift_self: true,
});
} }
// Delete folder and all horizontal inputs // Delete folder and all horizontal inputs, also deletes node in metadata
responses.add(NodeGraphMessage::DeleteNodes { responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![folder.to_node()], node_ids: vec![folder.to_node()],
reconnect: true, reconnect: true,
}); });
} }
if run_document_graph_after { responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
responses.add(DocumentMessage::DocumentStructureChanged); responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
@@ -1385,23 +1242,6 @@ impl DocumentMessageHandler {
self.saved_hash = None; self.saved_hash = None;
} }
} }
// TODO: Replace with disconnect message
pub fn disconnect_input(layer_to_disconnect_node: &mut DocumentNode, input_index: usize) {
let Some(node_type) = resolve_document_node_type(&layer_to_disconnect_node.name) else {
warn!("Node {} not in library", layer_to_disconnect_node.name);
return;
};
let Some(existing_input) = layer_to_disconnect_node.inputs.get_mut(input_index) else {
warn!("Node does not have and input at the selected index");
return;
};
let mut default_input = node_type.inputs[input_index].default.clone();
if let NodeInput::Value { exposed, .. } = &mut default_input {
*exposed = existing_input.is_exposed();
}
*existing_input = default_input;
}
pub fn get_downstream_node(network: &NodeNetwork, metadata: &DocumentMetadata, layer_to_move: LayerNodeIdentifier) -> Option<(NodeId, usize)> { pub fn get_downstream_node(network: &NodeNetwork, metadata: &DocumentMetadata, layer_to_move: LayerNodeIdentifier) -> Option<(NodeId, usize)> {
let mut downstream_layer = None; let mut downstream_layer = None;
@@ -1442,35 +1282,6 @@ impl DocumentMessageHandler {
}) })
} }
// TODO: move into message
pub fn disconnect_node(&mut self, layer_to_disconnect: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, layer_to_disconnect) else {
log::error!("Downstream node should always exist when moving layer");
return;
};
let layer_to_move_sibling_input = self.network.nodes.get(&layer_to_disconnect.to_node()).and_then(|node| node.inputs.get(0));
if let Some(NodeInput::Node { node_id, .. }) = layer_to_move_sibling_input {
let upstream_sibling_id = node_id.clone();
let Some(downstream_node) = self.network.nodes.get_mut(&downstream_node_id) else { return };
if let Some(NodeInput::Node { node_id, .. }) = downstream_node.inputs.get_mut(downstream_input_index) {
*node_id = upstream_sibling_id;
}
let upstream_shift = IVec2::new(0, -3);
let mut modify_inputs = ModifyInputsContext::new(&mut self.network, &mut self.metadata, &mut self.node_graph_handler, responses);
modify_inputs.shift_upstream(upstream_sibling_id, upstream_shift, true);
} else {
// Disconnect node directly downstream if upstream sibling doesn't exist
let Some(downstream_node) = self.network.nodes.get_mut(&downstream_node_id) else { return };
DocumentMessageHandler::disconnect_input(downstream_node, downstream_input_index);
}
let Some(to_move) = self.network.nodes.get_mut(&layer_to_disconnect.to_node()) else { return };
DocumentMessageHandler::disconnect_input(to_move, 0);
}
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index). /// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
/// ///
/// This function updates the insert index so that it points to the same place after the specified `layers` are deleted. /// This function updates the insert index so that it points to the same place after the specified `layers` are deleted.
@@ -1481,12 +1292,32 @@ impl DocumentMessageHandler {
} }
/// Finds the parent folder which, based on the current selections, should be the container of any newly added layers. /// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.
pub fn new_layer_parent(&self) -> LayerNodeIdentifier { pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {
self.metadata() self.metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true) .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), include_self)
.unwrap_or_else(|| self.metadata().active_artboard()) .unwrap_or_else(|| self.metadata().active_artboard())
} }
fn get_calculated_insert_index(&self, parent: LayerNodeIdentifier) -> isize {
parent
.children(self.metadata())
.enumerate()
.find_map(|(index, direct_child)| {
if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == direct_child) {
return Some(index as isize);
}
for descendant in direct_child.descendants(self.metadata()) {
if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == descendant) {
return Some(index as isize);
}
}
None
})
.unwrap_or(-1)
}
/// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document. /// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document.
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) { pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
let mut fonts = HashSet::new(); let mut fonts = HashSet::new();
@@ -19,10 +19,36 @@ use glam::{DAffine2, DVec2, IVec2};
#[impl_message(Message, DocumentMessage, GraphOperation)] #[impl_message(Message, DocumentMessage, GraphOperation)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum GraphOperationMessage { pub enum GraphOperationMessage {
AddNodesAsChild {
nodes: HashMap<NodeId, DocumentNode>,
parent: LayerNodeIdentifier,
insert_index: isize,
},
DisconnectInput {
node_id: NodeId,
input_index: usize,
},
FillSet { FillSet {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
fill: Fill, fill: Fill,
}, },
InsertLayerAtStackIndex {
layer_id: NodeId,
parent: NodeId,
insert_index: usize,
},
InsertNodeBetween {
post_node_id: NodeId,
post_node_input_index: usize,
insert_node_output_index: usize,
insert_node_id: NodeId,
insert_node_input_index: usize,
pre_node_output_index: usize,
pre_node_id: NodeId,
},
MoveSelectedSiblingsToChild {
new_parent: NodeId,
},
OpacitySet { OpacitySet {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
opacity: f64, opacity: f64,
@@ -64,10 +90,6 @@ pub enum GraphOperationMessage {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
strokes: Vec<BrushStroke>, strokes: Vec<BrushStroke>,
}, },
MoveUpstreamSiblingsToChild {
new_parent: NodeId,
upstream_sibling_ids: Vec<NodeId>,
},
NewArtboard { NewArtboard {
id: NodeId, id: NodeId,
artboard: Artboard, artboard: Artboard,
@@ -1,5 +1,6 @@
use super::transform_utils::{self, LayerBounds}; use super::transform_utils::{self, LayerBounds};
use super::utility_types::ModifyInputsContext; use super::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, SelectedNodes};
use crate::messages::prelude::*; use crate::messages::prelude::*;
@@ -36,11 +37,185 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
} = data; } = data;
match message { match message {
GraphOperationMessage::AddNodesAsChild { nodes, parent, insert_index } => {
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, NodeId(generate_uuid()))).collect();
let shift = nodes
.get(&NodeId(0))
.and_then(|node| {
document_network
.nodes
.get(&parent.to_node())
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
// Insert node into network
document_network.nodes.insert(node_id, document_node);
}
let Some(new_layer_id) = new_ids.get(&NodeId(0)) else {
log::error!("Could not get layer node when adding as child");
return;
};
let insert_index = if insert_index < 0 { 0 } else { insert_index as usize };
let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent.to_node(), insert_index);
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] });
if let Some(upstream_node) = upstream_node {
responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: downstream_node,
post_node_input_index: input_index,
insert_node_output_index: 0,
insert_node_id: *new_layer_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
})
} else {
responses.add(NodeGraphMessage::SetNodeInput {
node_id: downstream_node,
input_index: input_index,
input: NodeInput::node(*new_layer_id, 0),
})
}
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: *new_layer_id,
shift: IVec2::new(0, 3),
shift_self: true,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::DisconnectInput { node_id, input_index } => {
let Some(node_to_disconnect) = document_network.nodes.get(&node_id) else {
warn!("Node {} not found in DisconnectInput", node_id);
return;
};
let Some(node_type) = resolve_document_node_type(&node_to_disconnect.name) else {
warn!("Node {} not in library", node_to_disconnect.name);
return;
};
let Some(existing_input) = node_to_disconnect.inputs.get(input_index) else {
warn!("Node does not have an input at the selected index");
return;
};
let mut input = node_type.inputs[input_index].default.clone();
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = existing_input.is_exposed();
}
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
}
GraphOperationMessage::FillSet { layer, fill } => { GraphOperationMessage::FillSet { layer, fill } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.fill_set(fill); modify_inputs.fill_set(fill);
} }
} }
GraphOperationMessage::InsertLayerAtStackIndex { layer_id, parent, insert_index } => {
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&document_network, parent, insert_index);
// `layer_to_move` should always correspond to a node.
let Some(layer_to_move_node) = document_network.nodes.get(&layer_id) else {
log::error!("Layer node not found when inserting node {} at index {}", layer_id, insert_index);
return;
};
// Move current layer to post node.
let post_node = document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node");
let current_position = layer_to_move_node.metadata.position;
let new_position = post_node.metadata.position;
// If moved to top of a layer stack, move to the left of the post node. If moved within a stack, move directly on the post node. The stack will be shifted down later.
let offset_to_post_node = if insert_index == 0 {
new_position - current_position - IVec2::new(8, 0)
} else {
new_position - current_position
};
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: layer_id,
shift: offset_to_post_node,
shift_self: true,
});
// Update post_node input to layer_to_move.
if let Some(upstream_node) = pre_node_id {
responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: post_node_id,
post_node_input_index: post_node_input_index,
insert_node_output_index: 0,
insert_node_id: layer_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
})
} else {
responses.add(NodeGraphMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: NodeInput::node(layer_id, 0),
})
}
// Shift stack down, starting at the moved node.
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: layer_id,
shift: IVec2::new(0, 3),
shift_self: true,
});
}
GraphOperationMessage::InsertNodeBetween {
post_node_id,
post_node_input_index,
insert_node_output_index,
insert_node_id,
insert_node_input_index,
pre_node_output_index,
pre_node_id,
} => {
let Some(post_node) = document_network.nodes.get(&post_node_id) else {
error!("Post node not found");
return;
};
let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else {
error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
return;
};
let Some(insert_node) = document_network.nodes.get(&insert_node_id) else {
error!("Insert node not found");
return;
};
let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else {
error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}");
return;
};
let post_input = NodeInput::node(insert_node_id, insert_node_output_index);
responses.add(NodeGraphMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: post_input,
});
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index);
responses.add(NodeGraphMessage::SetNodeInput {
node_id: insert_node_id,
input_index: insert_node_input_index,
input: insert_input,
});
}
GraphOperationMessage::OpacitySet { layer, opacity } => { GraphOperationMessage::OpacitySet { layer, opacity } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) { if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.opacity_set(opacity); modify_inputs.opacity_set(opacity);
@@ -103,67 +278,78 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
modify_inputs.brush_modify(strokes); modify_inputs.brush_modify(strokes);
} }
} }
GraphOperationMessage::MoveUpstreamSiblingsToChild { new_parent, upstream_sibling_ids } => { GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => {
// Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec let group_layer = LayerNodeIdentifier::new(new_parent, &document_network);
for node_to_move in upstream_sibling_ids.iter().rev() { let Some(group_parent) = group_layer.parent(&document_metadata) else {
// Connect pre node to post node, or disconnect pre node if post node doesn't exist log::error!("Could not find parent for layer {:?}", group_layer);
let mut pre_node_id = new_parent;
loop {
let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get(&pre_node_id).and_then(|node| node.inputs.get(0)) else {
log::error!("End of stack should never be reached");
return;
};
if *node_id == *node_to_move {
break;
}
pre_node_id = *node_id;
}
if let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get(&node_to_move).and_then(|node| node.inputs.get(0)) {
let post_node_id = *node_id;
let Some(NodeInput::Node { node_id, .. }) = document_network.nodes.get_mut(&pre_node_id).and_then(|node| node.inputs.get_mut(0)) else {
log::error!("Pre node should always have primary input");
return;
};
*node_id = post_node_id;
} else {
DocumentMessageHandler::disconnect_input(document_network.nodes.get_mut(&pre_node_id).expect("Upstream sibling should always exist"), 0);
}
// Connect upstream sibling to the secondary input of the parent
let Some(parent_secondary_input) = document_network.nodes.get(&new_parent).and_then(|node| node.inputs.get(1)) else {
log::error!("Could not get child node input for current node");
return;
};
// Insert upstream_sibling_node at top of group stack
if let NodeInput::Node { node_id, .. } = parent_secondary_input {
// If there is already a node at the top of the stack, insert upstream_sibling_node in between
let current_child = *node_id;
let Some(upstream_sibling_input) = document_network.nodes.get_mut(&node_to_move).and_then(|node| node.inputs.get_mut(0)) else {
log::error!("Could not get upstream sibling node input");
return;
};
*upstream_sibling_input = NodeInput::node(current_child, 0);
}
let Some(parent_secondary_input_mut) = document_network.nodes.get_mut(&new_parent).and_then(|node| node.inputs.get_mut(1)) else {
log::error!("Could not get child node input for current node");
return;
};
*parent_secondary_input_mut = NodeInput::node(*node_to_move, 0);
}
let Some(most_upstream_sibling) = upstream_sibling_ids.last() else {
return; return;
}; };
DocumentMessageHandler::disconnect_input(document_network.nodes.get_mut(&most_upstream_sibling).expect("Upstream sibling should always exist"), 0);
let top_of_stack = upstream_sibling_ids.first().expect("Upstream nodes to move cannot be empty"); // Create a vec of nodes to move with all selected layers in the parent layer child stack, as well as each non layer sibling directly upstream of the selected layer
let upstream_shift = IVec2::new(-8, 0); let mut selected_siblings = Vec::new();
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
modify_inputs.shift_upstream(*top_of_stack, upstream_shift, true); // Skip over horizontal non layer node chain that feeds into parent
let Some(mut current_stack_node_id) = group_parent.first_child(&document_metadata).and_then(|current_stack_node| Some(current_stack_node.to_node())) else {
log::error!("Folder should always have child");
return;
};
let current_stack_node_id = &mut current_stack_node_id;
loop {
let mut current_stack_node = document_network.nodes.get(current_stack_node_id).expect("Current stack node id should always be a node");
// Check if the current stack node is a selected layer
if selected_nodes
.selected_layers(&document_metadata)
.any(|selected_node_id| selected_node_id.to_node() == *current_stack_node_id)
{
selected_siblings.push(*current_stack_node_id);
// Push all non layer sibling nodes directly upstream of the selected layer
loop {
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break };
let next_node = document_network.nodes.get(node_id).expect("Stack node id should always be a node");
// If the next node is a layer, immediately break and leave current stack node as the non layer node
if next_node.is_layer {
break;
}
*current_stack_node_id = *node_id;
current_stack_node = next_node;
selected_siblings.push(*current_stack_node_id);
}
}
// Get next node
let Some(NodeInput::Node { node_id, .. }) = current_stack_node.inputs.get(0) else { break };
*current_stack_node_id = *node_id;
}
// Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec
for node_to_move in selected_siblings.iter().rev() {
// Connect downstream node to upstream node, or disconnect downstream node if upstream node doesn't exist
responses.add(NodeGraphMessage::DisconnectLayerFromStack {
node_id: *node_to_move,
reconnect_to_sibling: true,
});
responses.add(GraphOperationMessage::InsertLayerAtStackIndex {
layer_id: *node_to_move,
parent: new_parent,
insert_index: 0,
});
}
let Some(most_upstream_sibling) = selected_siblings.last() else {
return;
};
responses.add(GraphOperationMessage::DisconnectInput {
node_id: *most_upstream_sibling,
input_index: 0,
});
} }
GraphOperationMessage::NewArtboard { id, artboard } => { GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
@@ -1,5 +1,6 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
use glam::IVec2;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
@@ -36,6 +37,10 @@ pub enum NodeGraphMessage {
node_id: NodeId, node_id: NodeId,
input_index: usize, input_index: usize,
}, },
DisconnectLayerFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
EnterNestedNetwork { EnterNestedNetwork {
node: NodeId, node: NodeId,
}, },
@@ -92,6 +97,10 @@ pub enum NodeGraphMessage {
input_index: usize, input_index: usize,
input: NodeInput, input: NodeInput,
}, },
SetNodePosition {
node_id: NodeId,
position: IVec2,
},
SetQualifiedInputValue { SetQualifiedInputValue {
node_path: Vec<NodeId>, node_path: Vec<NodeId>,
input_index: usize, input_index: usize,
@@ -101,6 +110,11 @@ pub enum NodeGraphMessage {
ShiftNode { ShiftNode {
node_id: NodeId, node_id: NodeId,
}, },
ShiftUpstream {
node_id: NodeId,
shift: IVec2,
shift_self: bool,
},
SetVisibility { SetVisibility {
node_id: NodeId, node_id: NodeId,
visible: bool, visible: bool,
@@ -9,6 +9,7 @@ use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::macros::action_keys; use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::load_network_structure; use crate::messages::portfolio::document::graph_operation::load_network_structure;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_types::{resolve_document_node_type, DocumentInputType, NodePropertiesContext}; use crate::messages::portfolio::document::node_graph::document_node_types::{resolve_document_node_type, DocumentInputType, NodePropertiesContext};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes}; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes};
@@ -161,15 +162,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let outward_links = document_network.collect_outwards_links(); let outward_links = document_network.collect_outwards_links();
for (_, upstream_id) in document_network.upstream_flow_back_from_nodes(vec![*child_id], graph_craft::document::FlowType::UpstreamFlow) { for (_, upstream_id) in document_network.upstream_flow_back_from_nodes(vec![*child_id], graph_craft::document::FlowType::UpstreamFlow) {
// TODO: move into a document_network function .is_sole_dependent. This function does a downstream traversal starting from the current node, // This does a downstream traversal starting from the current node, and ending at either a node in the delete_nodes set or the output.
// TODO: and only traverses for nodes that are not in the delete_nodes set. If all downstream nodes converge to some node in the delete_nodes set, // If the traversal find as child node of a node in the delete_nodes set, then it is a sole dependent. If the output node is eventually reached, then it is not a sole dependent.
// TODO: then it is a sole dependent. If the output node is eventually reached, then it is not a sole dependent. This means disconnected branches
// TODO: that do not feed into the delete_nodes set or the output node will be deleted.
let mut stack = vec![upstream_id]; let mut stack = vec![upstream_id];
let mut can_delete = true; let mut can_delete = true;
// TODO: Add iteration limit to force break in case of infinite while loop
while let Some(current_node) = stack.pop() { while let Some(current_node) = stack.pop() {
if let Some(downstream_nodes) = outward_links.get(&current_node) { if let Some(downstream_nodes) = outward_links.get(&current_node) {
for downstream_node in downstream_nodes { for downstream_node in downstream_nodes {
@@ -238,35 +235,65 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}); });
} }
NodeGraphMessage::DisconnectNodes { node_id, input_index } => { NodeGraphMessage::DisconnectNodes { node_id, input_index } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::DisconnectInput { node_id, input_index });
let Some(network) = document_network.nested_network(&self.network) else { let Some(network) = document_network.nested_network(&self.network) else {
warn!("No network"); warn!("No network");
return; return;
}; };
let Some(node) = network.nodes.get(&node_id) else {
warn!("Invalid node");
return;
};
let Some(node_type) = resolve_document_node_type(&node.name) else {
warn!("Node {} not in library", node.name);
return;
};
let Some((input_index, existing_input)) = node.inputs.iter().enumerate().filter(|(_, input)| input.is_exposed()).nth(input_index) else {
return;
};
let mut input = node_type.inputs[input_index].default.clone();
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = existing_input.is_exposed();
}
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
if network.connected_to_output(node_id) { if network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
} }
NodeGraphMessage::DisconnectLayerFromStack { node_id, reconnect_to_sibling } => {
let Some(network) = document_network.nested_network(&self.network) else {
warn!("No network");
return;
};
// Ensure node is a layer and create LayerNodeIdentifier
if network.nodes.get(&node_id).is_some_and(|node| !node.is_layer) {
log::error!("Non layer node passed to DisconnectLayer");
return;
}
let layer_to_disconnect = LayerNodeIdentifier::new(node_id, &network);
let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&network, &document_metadata, layer_to_disconnect) else {
log::error!("Downstream node should always exist when moving layer");
return;
};
let layer_to_move_sibling_input = network.nodes.get(&layer_to_disconnect.to_node()).and_then(|node| node.inputs.get(0));
if let Some(NodeInput::Node { node_id, .. }) = layer_to_move_sibling_input.and_then(|node_input| if reconnect_to_sibling { Some(node_input) } else { None }) {
let upstream_sibling_id = *node_id;
let Some(downstream_node) = document_network.nodes.get_mut(&downstream_node_id) else { return };
if let Some(NodeInput::Node { node_id, .. }) = downstream_node.inputs.get_mut(downstream_input_index) {
*node_id = upstream_sibling_id;
}
let upstream_shift = IVec2::new(0, -3);
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: upstream_sibling_id,
shift: upstream_shift,
shift_self: true,
});
} else {
// Disconnect node directly downstream if upstream sibling doesn't exist
responses.add(GraphOperationMessage::DisconnectInput {
node_id: downstream_node_id,
input_index: downstream_input_index,
});
}
responses.add(GraphOperationMessage::DisconnectInput {
node_id: layer_to_disconnect.to_node(),
input_index: 0,
});
}
NodeGraphMessage::EnterNestedNetwork { node } => { NodeGraphMessage::EnterNestedNetwork { node } => {
if let Some(network) = document_network.nested_network(&self.network) { if let Some(network) = document_network.nested_network(&self.network) {
if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() { if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() {
@@ -370,37 +397,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
error!("No network"); error!("No network");
return; return;
}; };
let Some(post_node) = network.nodes.get(&post_node_id) else {
error!("Post node not found");
return;
};
let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else {
error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
return;
};
let Some(insert_node) = network.nodes.get(&insert_node_id) else {
error!("Insert node not found");
return;
};
let Some((insert_node_input_index, _)) = insert_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(insert_node_input_index) else {
error!("Failed to find input index {insert_node_input_index} on node {insert_node_id:#?}");
return;
};
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let post_input = NodeInput::node(insert_node_id, insert_node_output_index); responses.add(GraphOperationMessage::InsertNodeBetween {
responses.add(NodeGraphMessage::SetNodeInput { post_node_id,
node_id: post_node_id, post_node_input_index,
input_index: post_node_input_index, insert_node_output_index,
input: post_input, insert_node_id,
}); insert_node_input_index,
pre_node_output_index,
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index); pre_node_id,
responses.add(NodeGraphMessage::SetNodeInput {
node_id: insert_node_id,
input_index: insert_node_input_index,
input: insert_input,
}); });
if network.connected_to_output(insert_node_id) { if network.connected_to_output(insert_node_id) {
@@ -520,6 +526,26 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} }
} }
} }
NodeGraphMessage::SetNodePosition { node_id, position } => {
let Some(network) = document_network.nested_network_mut(&self.network) else {
warn!("No network");
return;
};
let Some(node) = network.nodes.get_mut(&node_id) else {
log::error!("Failed to find node {node_id} when setting position");
return;
};
node.metadata.position = position;
// Since document structure doesn't change, just update the nodes
if graph_view_overlay_open {
let links = Self::collect_links(network);
let nodes = self.collect_nodes(&links, network);
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
}
}
NodeGraphMessage::SetQualifiedInputValue { node_path, input_index, value } => { NodeGraphMessage::SetQualifiedInputValue { node_path, input_index, value } => {
let Some((node_id, node_path)) = node_path.split_last() else { let Some((node_id, node_path)) = node_path.split_last() else {
error!("Node path is empty"); error!("Node path is empty");
@@ -591,6 +617,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses); self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses);
} }
NodeGraphMessage::ShiftUpstream { node_id, shift, shift_self } => {
let Some(network) = document_network.nested_network_mut(&self.network) else {
warn!("No network");
return;
};
let mut modify_inputs = ModifyInputsContext::new(network, document_metadata, self, responses);
modify_inputs.shift_upstream(node_id, shift, shift_self);
}
NodeGraphMessage::ToggleSelectedVisibility => { NodeGraphMessage::ToggleSelectedVisibility => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
@@ -183,30 +183,29 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
let ordered_last_elements: Vec<_> = active_document.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).collect(); let ordered_last_elements: Vec<_> = active_document.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).collect();
for layer in ordered_last_elements { for layer in ordered_last_elements {
let node = layer.to_node(); let layer_node_id = layer.to_node();
let previous_alias = active_document.network().nodes.get(&node).map(|node| node.alias.clone()).unwrap_or_default(); let previous_alias = active_document.network().nodes.get(&layer_node_id).map(|node| node.alias.clone()).unwrap_or_default();
let Some(node) = active_document let mut copy_ids = HashMap::new();
copy_ids.insert(layer_node_id, NodeId(0 as u64));
if let Some(input_node) = active_document
.network() .network()
.nodes .nodes
.get(&node) .get(&layer_node_id)
.and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.get(0) }) .and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.get(0) })
.and_then(|input| input.as_node()) .and_then(|input| input.as_node())
else { {
continue; active_document
.network()
.upstream_flow_back_from_nodes(vec![input_node], graph_craft::document::FlowType::UpstreamFlow)
.enumerate()
.for_each(|(index, (_, node_id))| {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
}; };
buffer.push(CopyBufferEntry { buffer.push(CopyBufferEntry {
nodes: NodeGraphMessageHandler::copy_nodes( nodes: NodeGraphMessageHandler::copy_nodes(active_document.network(), &copy_ids).collect(),
active_document.network(),
&active_document
.network()
.upstream_flow_back_from_nodes(vec![node], graph_craft::document::FlowType::UpstreamFlow)
.enumerate()
.map(|(index, (_, node_id))| (node_id, NodeId(index as u64)))
.collect(),
)
.collect(),
selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()), selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()),
visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()), visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()),
locked: active_document.selected_nodes.layer_locked(layer, active_document.metadata()), locked: active_document.selected_nodes.layer_locked(layer, active_document.metadata()),
@@ -384,26 +383,17 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| { let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {
if self.active_document().is_some() { if self.active_document().is_some() {
trace!("Pasting into folder {parent:?} as index: {insert_index}"); trace!("Pasting into folder {parent:?} as index: {insert_index}");
let id = NodeId(generate_uuid());
responses.add(GraphOperationMessage::NewCustomLayer { responses.add(GraphOperationMessage::AddNodesAsChild {
id, nodes: entry.clone().nodes,
nodes: entry.nodes.clone(),
parent, parent,
insert_index, insert_index,
alias: entry.alias.clone(),
}); });
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
if !entry.visible {
responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
}
if entry.locked {
responses.add(NodeGraphMessage::SetLocked { node_id: id, locked: true });
}
} }
}; };
responses.add(DocumentMessage::DeselectAllLayers);
for entry in self.copy_buffer[clipboard as usize].iter().rev() { for entry in self.copy_buffer[clipboard as usize].iter().rev() {
paste(entry, responses) paste(entry, responses)
} }
@@ -411,30 +401,19 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
PortfolioMessage::PasteSerializedData { data } => { PortfolioMessage::PasteSerializedData { data } => {
if let Some(document) = self.active_document() { if let Some(document) = self.active_document() {
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) { if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
let parent = document.new_layer_parent(); let parent = document.new_layer_parent(false);
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
for entry in data.into_iter().rev() { for entry in data.into_iter().rev() {
document.load_layer_resources(responses); document.load_layer_resources(responses);
let id = NodeId(generate_uuid()); responses.add(GraphOperationMessage::AddNodesAsChild {
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes: entry.nodes, nodes: entry.nodes,
parent, parent,
insert_index: -1, insert_index: -1,
alias: entry.alias,
}); });
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
if !entry.visible {
responses.add(NodeGraphMessage::SetVisibility { node_id: id, visible: false });
}
} }
responses.add(DocumentMessage::CommitTransaction);
} }
} }
} }
@@ -420,7 +420,7 @@ fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<M
responses.add(GraphOperationMessage::NewCustomLayer { responses.add(GraphOperationMessage::NewCustomLayer {
id, id,
nodes: HashMap::from([(NodeId(0), brush_node)]), nodes: HashMap::from([(NodeId(0), brush_node)]),
parent: document.new_layer_parent(), parent: document.new_layer_parent(true),
insert_index: -1, insert_index: -1,
alias: String::new(), alias: String::new(),
}); });
@@ -202,7 +202,7 @@ impl Fsm for EllipseToolFsmState {
// Create a new ellipse vector shape // Create a new ellipse vector shape
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE); let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
let manipulator_groups = subpath.manipulator_groups().to_vec(); let manipulator_groups = subpath.manipulator_groups().to_vec();
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses); graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses);
shape_data.layer = Some(layer); shape_data.layer = Some(layer);
@@ -230,7 +230,7 @@ impl Fsm for FreehandToolFsmState {
} else { } else {
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
let parent = document.new_layer_parent(); let parent = document.new_layer_parent(true);
let transform = document.metadata().transform_to_viewport(parent); let transform = document.metadata().transform_to_viewport(parent);
let pos = transform.inverse().transform_point2(input.mouse.position); let pos = transform.inverse().transform_point2(input.mouse.position);
let subpath = bezier_rs::Subpath::from_anchors([pos], false); let subpath = bezier_rs::Subpath::from_anchors([pos], false);
@@ -181,7 +181,7 @@ impl Fsm for LineToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
responses.add(GraphOperationMessage::StrokeSet { responses.add(GraphOperationMessage::StrokeSet {
layer, layer,
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight), stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
@@ -232,7 +232,7 @@ impl PenToolData {
input: &InputPreprocessorMessageHandler, input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
) { ) {
let parent = document.new_layer_parent(); let parent = document.new_layer_parent(true);
// Deselect layers because we are now creating a new layer // Deselect layers because we are now creating a new layer
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
@@ -247,7 +247,7 @@ impl Fsm for PolygonToolFsmState {
PolygonType::Convex => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.), PolygonType::Convex => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
PolygonType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5), PolygonType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
}; };
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
polygon_data.layer = Some(layer); polygon_data.layer = Some(layer);
let fill_color = tool_options.fill.active_color(); let fill_color = tool_options.fill.active_color();
@@ -209,7 +209,7 @@ impl Fsm for RectangleToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(), responses); let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
shape_data.layer = Some(layer); shape_data.layer = Some(layer);
let fill_color = tool_options.fill.active_color(); let fill_color = tool_options.fill.active_color();
@@ -208,7 +208,7 @@ impl Fsm for SplineToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
let parent = document.new_layer_parent(); let parent = document.new_layer_parent(true);
let transform = document.metadata().transform_to_viewport(parent); let transform = document.metadata().transform_to_viewport(parent);
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); //tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
@@ -283,7 +283,7 @@ impl TextToolData {
text: String::new(), text: String::new(),
font: editing_text.font.clone(), font: editing_text.font.clone(),
size: editing_text.font_size, size: editing_text.font_size,
parent: document.new_layer_parent(), parent: document.new_layer_parent(true),
insert_index: -1, insert_index: -1,
}); });
responses.add(GraphOperationMessage::FillSet { responses.add(GraphOperationMessage::FillSet {