mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Generalize layers as merge nodes to enable adjustment layers (#1712)
* WIP, backward traversal issues * Fix some tool issues * Remove debugging * Change some indices * WIP: new artboard node * WIP: add artboard node * WIP: Artboard node and create_artboard * WIP: Artboard node implementation complete * WIP: Artboards input for output node * Complete Artboard node * Generalize LayerNodeIdentifier, monitor_nodes support for Artboard node, adjust ResizeArtboard/ClearArtboards, move alias validation to Rust * Fix misaligned artboard click targets * Generalize/clarify create_layer and insert_between * non-negative dimensions for resize_artboard * Show artboards in layer panel * Generalize create_layer for layer output node * Generalize delete_layer/delete_artboard to NodeGraphMessage::DeleteNodes. Fixed upstream flow Iter * remove old primary_input function * Vertical node visuals, remove is_layer function, rename Layer node to Merge node, toggle display as layer exposed_value_count type fix Vertical node visuals, remove is_layer function, rename Layer node to Merge node, toggle display as layer * Fix demo artwork * Layer display context menu * Automatically select artboard, fix warnings * Improvements to context menu and layer invariant enforcement * Remove display_as_layer and update load_structure * Improve load_structure to show more layers, improve FlowIter, improve layer naming, layer rearrangement validation. * Clean up demo artwork using generalized layers * Improve design of Layers panel and graph nodes * MoveSelectedLayersTo rewrite to support generalized layer nodes * Include artboards in deepest_common_ancestor, fix resize_artboard/delete_artboard, sync artboard tool to layer panel * MoveSelectedLayersTo adjustments * Sync non layer node visibility with metadata * Include non layer nodes when moving/creating layer * Fix group layers and get_post_node_with_index * Include non layer nodes in UngroupSelectedLayers * GroupSelected for all selected nodes, UnGroupSelected position adjustments * Add grouping for layers in different folders * Fix hidden layers * Prevent node from connecting to itself, fix undo automatic node insertion, * Fix undo CreateEmptyFolder, fix grouping nested layer nodes * Formatting * Remove test and check if node is layer from network * Fix undo group layers * Check off roadmap * MoveUpstreamSiblingsToChild adjustments * Replace tabs with spaces, remove mut from argument * Final code review pass --------- Co-authored-by: 0hypercube <0hypercube@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -359,54 +359,6 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn copy_paste_folder() {
|
||||
let mut editor = create_editor_with_three_layers();
|
||||
|
||||
const FOLDER_ID: NodeId = NodeId(3);
|
||||
|
||||
editor.handle_message(GraphOperationMessage::NewCustomLayer {
|
||||
id: FOLDER_ID,
|
||||
nodes: HashMap::new(),
|
||||
parent: LayerNodeIdentifier::ROOT,
|
||||
insert_index: -1,
|
||||
alias: String::new(),
|
||||
});
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![FOLDER_ID] });
|
||||
|
||||
editor.drag_tool(ToolType::Line, 0., 0., 10., 10.);
|
||||
editor.drag_tool(ToolType::Freehand, 10., 20., 30., 40.);
|
||||
|
||||
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![FOLDER_ID] });
|
||||
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
|
||||
|
||||
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
parent: LayerNodeIdentifier::ROOT,
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
|
||||
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
|
||||
let [original_folder, original_freehand, original_line, original_ellipse, original_polygon, original_rect] = layers_before_copy[..] else {
|
||||
panic!("Layers before incorrect");
|
||||
};
|
||||
let [_, _, _, folder, freehand, line, ellipse, polygon, rect] = layers_after_copy[..] else {
|
||||
panic!("Layers after incorrect");
|
||||
};
|
||||
assert_eq!(original_folder, folder);
|
||||
assert_eq!(original_freehand, freehand);
|
||||
assert_eq!(original_line, line);
|
||||
assert_eq!(original_ellipse, ellipse);
|
||||
assert_eq!(original_polygon, polygon);
|
||||
assert_eq!(original_rect, rect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
/// - create rect, shape and ellipse
|
||||
|
||||
@@ -61,6 +61,7 @@ pub fn default_mapping() -> Mapping {
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=NodeGraphMessage::DuplicateSelectedNodes),
|
||||
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
|
||||
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),
|
||||
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedLayers),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),
|
||||
|
||||
@@ -5,12 +5,13 @@ 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::messages::input_mapper::utility_types::macros::action_keys;
|
||||
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::node_graph::document_node_types::resolve_document_node_type;
|
||||
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::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{is_artboard, is_folder, DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{is_artboard, DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
@@ -20,6 +21,7 @@ use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::FlowType;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::raster::ImageFrame;
|
||||
@@ -29,7 +31,7 @@ use graphene_core::vector::style::ViewMode;
|
||||
use graphene_core::{concrete, generic, ProtoNodeIdentifier};
|
||||
use graphene_std::wasm_application_io::WasmEditorApi;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
use std::vec;
|
||||
|
||||
@@ -238,7 +240,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
.enumerate()
|
||||
.find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index as isize))
|
||||
.unwrap_or(-1);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||
id,
|
||||
nodes: HashMap::new(),
|
||||
@@ -252,7 +254,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
info!("{:#?}", self.network);
|
||||
}
|
||||
DocumentMessage::DeleteLayer { id } => {
|
||||
responses.add(GraphOperationMessage::DeleteLayer { id });
|
||||
responses.add(NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true });
|
||||
responses.add_front(BroadcastEvent::ToolAbort);
|
||||
}
|
||||
DocumentMessage::DeleteSelectedLayers => {
|
||||
@@ -289,7 +291,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
self.network(),
|
||||
&self
|
||||
.network()
|
||||
.upstream_flow_back_from_nodes(vec![node], false)
|
||||
.upstream_flow_back_from_nodes(vec![node], FlowType::UpstreamFlow)
|
||||
.enumerate()
|
||||
.map(|(index, (_, node_id))| (node_id, NodeId(index as u64)))
|
||||
.collect(),
|
||||
@@ -356,9 +358,87 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
DocumentMessage::GroupSelectedLayers => {
|
||||
let parent = self
|
||||
.metadata()
|
||||
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true)
|
||||
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false)
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT);
|
||||
|
||||
// Cancel grouping layers across different artboards
|
||||
// TODO: Group each set of layers for each artboard separately
|
||||
if parent == LayerNodeIdentifier::ROOT {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move layers in nested unselected folders above the first unselected parent folder
|
||||
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
|
||||
for layer in selected_layers.clone() {
|
||||
let mut first_unselected_parent_folder = layer.parent(&self.metadata).expect("Layer should always have parent");
|
||||
|
||||
// Find folder in parent child stack
|
||||
loop {
|
||||
// Loop until parent layer is deselected. Note that parent cannot be selected, since it is an ancestor of all selected layers
|
||||
if !selected_layers.iter().any(|selected_layer| *selected_layer == first_unselected_parent_folder) {
|
||||
break;
|
||||
}
|
||||
let Some(new_folder) = first_unselected_parent_folder.parent(&self.metadata) else {
|
||||
log::error!("Layer should always have parent");
|
||||
return;
|
||||
};
|
||||
first_unselected_parent_folder = new_folder;
|
||||
}
|
||||
|
||||
// Don't move nodes above new group folder parent
|
||||
if first_unselected_parent_folder == parent {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Disconnect above and below the old layer location
|
||||
self.disconnect_node(layer, responses);
|
||||
|
||||
// Move disconnected node to folder
|
||||
let folder_position = self
|
||||
.network
|
||||
.nodes
|
||||
.get(&first_unselected_parent_folder.to_node())
|
||||
.expect("Current folder should always exist")
|
||||
.metadata
|
||||
.position;
|
||||
|
||||
let Some(layer_to_move_node_mut) = self.network.nodes.get_mut(&layer.to_node()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
DocumentMessageHandler::disconnect_input(layer_to_move_node_mut, 0);
|
||||
layer_to_move_node_mut.metadata.position = folder_position;
|
||||
|
||||
// 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)
|
||||
else {
|
||||
log::error!("Downstream node should always exist when inserting layer");
|
||||
return;
|
||||
};
|
||||
let downstream_input = self
|
||||
.network
|
||||
.nodes
|
||||
.get_mut(&folder_downstream_node_id)
|
||||
.and_then(|node| node.inputs.get_mut(folder_downstream_input_index));
|
||||
let Some(NodeInput::Node { node_id, .. }) = downstream_input else {
|
||||
log::error!("Downstream node should have a node input");
|
||||
return;
|
||||
};
|
||||
*node_id = layer.to_node();
|
||||
|
||||
// Connect layer primary input to parent folder
|
||||
let Some(layer_node_input) = self.network.nodes.get_mut(&layer.to_node()).and_then(|node| node.inputs.get_mut(0)) else {
|
||||
log::error!("Layer should always have primary input");
|
||||
return;
|
||||
};
|
||||
*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)| {
|
||||
if self.selected_nodes.selected_layers(self.metadata()).any(|selected| selected == direct_child) {
|
||||
return Some(index as isize);
|
||||
@@ -374,10 +454,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
});
|
||||
|
||||
let folder_id = NodeId(generate_uuid());
|
||||
|
||||
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||
id: folder_id,
|
||||
nodes: HashMap::new(),
|
||||
@@ -385,12 +462,60 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
insert_index: calculated_insert_index.unwrap_or(-1),
|
||||
alias: String::new(),
|
||||
});
|
||||
responses.add(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
parent: LayerNodeIdentifier::new_unchecked(folder_id),
|
||||
insert_index: -1,
|
||||
|
||||
// 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 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::RunDocumentGraph);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::ImaginateGenerate => responses.add(PortfolioMessage::SubmitGraphRender { document_id }),
|
||||
DocumentMessage::ImaginateRandom { imaginate_node, then_generate } => {
|
||||
@@ -430,22 +555,96 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
});
|
||||
}
|
||||
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
||||
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Disallow trying to insert into self
|
||||
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
|
||||
// Disallow trying to insert into self.
|
||||
if selected_layers.iter().any(|&layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer)) {
|
||||
return;
|
||||
}
|
||||
// Artboards can only have the Output node as the parent.
|
||||
if selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent != LayerNodeIdentifier::ROOT {
|
||||
return;
|
||||
}
|
||||
// Disallow inserting layers between artboards. Since only artboards can output to Output node, the layer parent cannot be the output.
|
||||
if !selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent == LayerNodeIdentifier::ROOT {
|
||||
return;
|
||||
}
|
||||
|
||||
let insert_index = self.update_insert_index(&selected_layers, parent, insert_index);
|
||||
|
||||
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
responses.add(DocumentMessage::DeleteSelectedLayers);
|
||||
responses.add(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
parent,
|
||||
insert_index,
|
||||
});
|
||||
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 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.
|
||||
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 {
|
||||
if !run_document_graph_after && self.network.connected_to_output(layer_to_move.to_node()) {
|
||||
run_document_graph_after = true;
|
||||
}
|
||||
|
||||
// Part 1: Disconnect layer to move and reconnect downstream node to upstream sibling if it exists.
|
||||
self.disconnect_node(layer_to_move, responses);
|
||||
|
||||
// Part 2: Reconnect layer_to_move to new parent at insert index.
|
||||
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&self.network, 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(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::NudgeSelectedLayers {
|
||||
delta_x,
|
||||
@@ -795,56 +994,100 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
DocumentMessage::UngroupSelectedLayers => {
|
||||
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 mut ungrouped_folders = HashSet::new();
|
||||
|
||||
for folder in folder_paths {
|
||||
// Select all the children of the folder
|
||||
let selected = folder
|
||||
.descendants(self.metadata())
|
||||
.filter_map(|descendant| {
|
||||
if ungrouped_folders.contains(&descendant.to_node()) {
|
||||
return None;
|
||||
if !run_document_graph_after && self.network.connected_to_output(folder.to_node()) {
|
||||
run_document_graph_after = true;
|
||||
}
|
||||
|
||||
// Cannot ungroup artboard
|
||||
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
|
||||
if folder_node.is_artboard() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get first child layer node that feeds into the secondary input for the folder
|
||||
let Some(child_layer_node_id) = folder.first_child(&self.metadata).map(|child_layer| child_layer.to_node()) else {
|
||||
log::error!("Folder should always have a child");
|
||||
return;
|
||||
};
|
||||
|
||||
// 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 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);
|
||||
modify_inputs.shift_upstream(child_layer_node_id, offset, true);
|
||||
|
||||
// Set the 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 {
|
||||
log::error!("Downstream node should always exist when moving layer");
|
||||
continue;
|
||||
};
|
||||
let Some(NodeInput::Node { node_id, .. }) = self
|
||||
.network
|
||||
.nodes
|
||||
.get_mut(&downstream_node_id)
|
||||
.expect("downstream node should always exist")
|
||||
.inputs
|
||||
.get_mut(downstream_input_index)
|
||||
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)
|
||||
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;
|
||||
|
||||
// Get the node at the bottom of the first layer node stack
|
||||
let mut last_child_node_id = child_layer_node_id;
|
||||
loop {
|
||||
let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&last_child_node_id).expect("Child node should always exist").inputs.get(0) else {
|
||||
break;
|
||||
};
|
||||
last_child_node_id = *node_id;
|
||||
}
|
||||
|
||||
let parent = descendant.parent(self.metadata()).expect("No parent");
|
||||
if parent != folder && !ungrouped_folders.contains(&parent.to_node()) {
|
||||
return None;
|
||||
}
|
||||
// Connect the primary input of the bottom layer to 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 {
|
||||
log::error!("Could not get last child node primary input");
|
||||
continue;
|
||||
};
|
||||
*node_input = NodeInput::node(upstream_sibling_id, 0);
|
||||
|
||||
Some(descendant.to_node())
|
||||
})
|
||||
.collect::<Vec<NodeId>>();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: selected });
|
||||
// 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 bottom_of_stack = self.network.nodes.get(&last_child_node_id).expect("Last child layer should always exist for last child layer id");
|
||||
let target_distance = bottom_of_stack.metadata.position.y - top_of_stack.metadata.position.y;
|
||||
|
||||
// Copy them
|
||||
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
|
||||
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 current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y;
|
||||
|
||||
// Paste them into the folder above
|
||||
let insert_index = folder
|
||||
.parent(self.metadata())
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT)
|
||||
.children(self.metadata())
|
||||
.enumerate()
|
||||
.find_map(|(index, item)| (item == folder).then_some(index as isize))
|
||||
.unwrap_or(-1);
|
||||
responses.add(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: Clipboard::Internal,
|
||||
parent: folder.parent(self.metadata()).unwrap_or(LayerNodeIdentifier::ROOT),
|
||||
insert_index,
|
||||
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);
|
||||
modify_inputs.shift_upstream(upstream_sibling_id, IVec2::new(0, y_offset), true);
|
||||
}
|
||||
|
||||
// Delete folder and all horizontal inputs
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![folder.to_node()],
|
||||
reconnect: true,
|
||||
});
|
||||
|
||||
// Mark the folder for deletion
|
||||
ungrouped_folders.insert(folder.to_node());
|
||||
}
|
||||
|
||||
for id in ungrouped_folders {
|
||||
responses.add(GraphOperationMessage::DeleteLayer { id });
|
||||
if run_document_graph_after {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::UpdateDocumentTransform { transform } => {
|
||||
self.metadata.document_to_viewport = transform;
|
||||
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
@@ -901,7 +1144,10 @@ impl DocumentMessageHandler {
|
||||
|
||||
/// Find the deepest layer given in the sorted array (by returning the one which is not a folder from the list of layers under the click location).
|
||||
pub fn find_deepest(&self, node_list: &[LayerNodeIdentifier], network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
|
||||
node_list.iter().find(|&&layer| !is_folder(layer, network)).copied()
|
||||
node_list
|
||||
.iter()
|
||||
.find(|&&layer| !network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default())
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Find any layers sorted by index that are under the given location in viewport space.
|
||||
@@ -912,7 +1158,12 @@ impl DocumentMessageHandler {
|
||||
/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
||||
pub fn click_list(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
|
||||
let mut node_list = self.click_list_any(viewport_location, network);
|
||||
node_list.truncate(node_list.iter().position(|&layer| !is_folder(layer, network)).unwrap_or(0) + 1);
|
||||
node_list.truncate(
|
||||
node_list
|
||||
.iter()
|
||||
.position(|&layer| !network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default())
|
||||
.unwrap_or(0) + 1,
|
||||
);
|
||||
node_list
|
||||
}
|
||||
|
||||
@@ -1134,23 +1385,109 @@ impl DocumentMessageHandler {
|
||||
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)> {
|
||||
let mut downstream_layer = None;
|
||||
if let Some(previous_sibling) = layer_to_move.previous_sibling(metadata) {
|
||||
downstream_layer = Some((previous_sibling.to_node(), false))
|
||||
} else if let Some(parent) = layer_to_move.parent(metadata) {
|
||||
downstream_layer = Some((parent.to_node(), true))
|
||||
};
|
||||
|
||||
// Downstream layer should always exist
|
||||
let Some((downstream_layer_node_id, downstream_layer_is_parent)) = downstream_layer else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Horizontal traversal if layer_to_move is the top of its layer stack, primary traversal if not
|
||||
let flow_type = if downstream_layer_is_parent { FlowType::HorizontalFlow } else { FlowType::PrimaryFlow };
|
||||
|
||||
network
|
||||
.upstream_flow_back_from_nodes(vec![downstream_layer_node_id], flow_type)
|
||||
.find(|(node, node_id)| {
|
||||
// Get secondary input only if it is the downstream_layer_node_id, the parent of layer to move, and a layer node (parent might be output)
|
||||
let is_parent_layer = downstream_layer_is_parent && downstream_layer_node_id == *node_id && node.is_layer;
|
||||
let node_input_index = if is_parent_layer { 1 } else { 0 };
|
||||
|
||||
node.inputs.get(node_input_index).is_some_and(|node_input| {
|
||||
if let NodeInput::Node { node_id, .. } = node_input {
|
||||
*node_id == layer_to_move.to_node()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
})
|
||||
.map(|(downstream_node, downstream_node_id)| {
|
||||
let is_parent_layer = downstream_layer_is_parent && downstream_layer_node_id == downstream_node_id && downstream_node.is_layer;
|
||||
let downstream_input_index = if is_parent_layer { 1 } else { 0 };
|
||||
|
||||
(downstream_node_id, downstream_input_index)
|
||||
})
|
||||
}
|
||||
|
||||
// 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).
|
||||
///
|
||||
/// This function updates the insert index so that it points to the same place after the specified `layers` are deleted.
|
||||
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> isize {
|
||||
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> usize {
|
||||
let take_amount = if insert_index < 0 { usize::MAX } else { insert_index as usize };
|
||||
let layer_ids_above = parent.children(self.metadata()).take(take_amount);
|
||||
layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as isize
|
||||
layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as usize
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.metadata()
|
||||
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false)
|
||||
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true)
|
||||
.unwrap_or_else(|| self.metadata().active_artboard())
|
||||
}
|
||||
|
||||
/// 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>) {
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node) in self.network.recursive_nodes() {
|
||||
@@ -1572,7 +1909,7 @@ impl DocumentMessageHandler {
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
//
|
||||
IconButton::new("NewLayer", 24)
|
||||
.tooltip("New Folder/Layer")
|
||||
.tooltip("New Layer")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::CreateEmptyFolder))
|
||||
.on_update(|_| DocumentMessage::CreateEmptyFolder.into())
|
||||
.widget_holder(),
|
||||
@@ -1649,7 +1986,6 @@ impl DocumentMessageHandler {
|
||||
|
||||
// If moving down, insert below this layer. If moving up, insert above this layer.
|
||||
let insert_index = if relative_index_offset < 0 { neighbor_index } else { neighbor_index + 1 } as isize;
|
||||
|
||||
responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,10 @@ pub enum GraphOperationMessage {
|
||||
layer: LayerNodeIdentifier,
|
||||
strokes: Vec<BrushStroke>,
|
||||
},
|
||||
MoveUpstreamSiblingsToChild {
|
||||
new_parent: NodeId,
|
||||
upstream_sibling_ids: Vec<NodeId>,
|
||||
},
|
||||
NewArtboard {
|
||||
id: NodeId,
|
||||
artboard: Artboard,
|
||||
@@ -100,12 +104,6 @@ pub enum GraphOperationMessage {
|
||||
location: IVec2,
|
||||
dimensions: IVec2,
|
||||
},
|
||||
DeleteLayer {
|
||||
id: NodeId,
|
||||
},
|
||||
DeleteArtboard {
|
||||
id: NodeId,
|
||||
},
|
||||
ClearArtboards,
|
||||
NewSvg {
|
||||
id: NodeId,
|
||||
|
||||
@@ -103,10 +103,72 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
modify_inputs.brush_modify(strokes);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::MoveUpstreamSiblingsToChild { new_parent, upstream_sibling_ids } => {
|
||||
// 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 upstream_sibling_ids.iter().rev() {
|
||||
// Connect pre node to post node, or disconnect pre node if post node doesn't exist
|
||||
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;
|
||||
};
|
||||
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");
|
||||
let upstream_shift = IVec2::new(-8, 0);
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
modify_inputs.shift_upstream(*top_of_stack, upstream_shift, true);
|
||||
}
|
||||
GraphOperationMessage::NewArtboard { id, artboard } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.document_network.original_outputs()[0].node_id, 0, 0) {
|
||||
modify_inputs.insert_artboard(artboard, layer);
|
||||
if let Some(artboard_id) = modify_inputs.create_artboard(id, artboard) {
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![artboard_id] });
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
|
||||
}
|
||||
@@ -128,8 +190,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
insert_index,
|
||||
alias,
|
||||
} => {
|
||||
trace!("Inserting new layer {id} as a child of {parent:?} at index {insert_index}");
|
||||
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
|
||||
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
|
||||
@@ -164,11 +224,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
|
||||
if let Some(layer_node) = modify_inputs.document_network.nodes.get_mut(&layer) {
|
||||
if let Some(&input) = new_ids.get(&NodeId(0)) {
|
||||
layer_node.inputs[0] = NodeInput::node(input, 0)
|
||||
layer_node.inputs[1] = NodeInput::node(input, 0);
|
||||
}
|
||||
}
|
||||
|
||||
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
error!("Creating new custom layer failed");
|
||||
}
|
||||
|
||||
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
|
||||
@@ -199,37 +261,20 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
||||
modify_inputs.resize_artboard(location, dimensions);
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::DeleteLayer { id } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
modify_inputs.delete_layer(id, selected_nodes, false);
|
||||
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
|
||||
}
|
||||
GraphOperationMessage::DeleteArtboard { id } => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
if let Some(artboard_id) = modify_inputs.document_network.nodes.get(&id).and_then(|node| node.inputs[0].as_node()) {
|
||||
modify_inputs.delete_artboard(artboard_id, selected_nodes);
|
||||
} else {
|
||||
warn!("Artboard does not exist");
|
||||
}
|
||||
modify_inputs.delete_layer(id, selected_nodes, true);
|
||||
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
|
||||
}
|
||||
GraphOperationMessage::ClearArtboards => {
|
||||
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
let layer_nodes = modify_inputs.document_network.nodes.iter().filter(|(_, node)| node.is_layer()).map(|(id, _)| *id).collect::<Vec<_>>();
|
||||
for layer in layer_nodes {
|
||||
let artboards = modify_inputs
|
||||
.document_network
|
||||
.upstream_flow_back_from_nodes(vec![layer], true)
|
||||
.filter_map(|(node, _id)| if node.is_artboard() { Some(_id) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
if artboards.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for artboard in artboards {
|
||||
modify_inputs.delete_artboard(artboard, selected_nodes);
|
||||
}
|
||||
modify_inputs.delete_layer(layer, selected_nodes, true);
|
||||
let modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
|
||||
let artboard_nodes = modify_inputs
|
||||
.document_network
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|(_, node)| node.is_artboard())
|
||||
.map(|(id, _)| *id)
|
||||
.collect::<Vec<_>>();
|
||||
for artboard in artboard_nodes {
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![artboard],
|
||||
reconnect: true,
|
||||
});
|
||||
}
|
||||
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
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::nodes::SelectedNodes;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
@@ -73,7 +72,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let mut document = Self::new(document_network, document_metadata, node_graph, responses);
|
||||
|
||||
let mut id = id;
|
||||
while !document.document_network.nodes.get(&id)?.is_layer() {
|
||||
while !document.document_network.nodes.get(&id)?.is_layer {
|
||||
id = document.outwards_links.get(&id)?.first().copied()?;
|
||||
}
|
||||
|
||||
@@ -87,18 +86,28 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
update_input(&mut document_node.inputs, node_id, self.document_metadata);
|
||||
}
|
||||
|
||||
pub fn insert_between(&mut self, id: NodeId, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize, shift_upstream: IVec2) -> Option<NodeId> {
|
||||
pub fn insert_between(
|
||||
&mut self,
|
||||
id: NodeId,
|
||||
mut new_node: DocumentNode,
|
||||
new_node_input: NodeInput,
|
||||
new_node_input_index: usize,
|
||||
post_node_id: NodeId,
|
||||
post_node_input: NodeInput,
|
||||
post_node_input_index: usize,
|
||||
shift_upstream: IVec2,
|
||||
) -> Option<NodeId> {
|
||||
assert!(!self.document_network.nodes.contains_key(&id), "Creating already existing node");
|
||||
let pre_node = self.document_network.nodes.get_mut(&pre.node_id)?;
|
||||
node.metadata.position = pre_node.metadata.position;
|
||||
let pre_node = self.document_network.nodes.get_mut(&new_node_input.as_node().expect("Input should reference a node"))?;
|
||||
new_node.metadata.position = pre_node.metadata.position;
|
||||
|
||||
let post_node = self.document_network.nodes.get_mut(&post.node_id)?;
|
||||
node.inputs[input] = NodeInput::node(pre.node_id, pre.node_output_index);
|
||||
post_node.inputs[post.node_output_index] = NodeInput::node(id, output);
|
||||
let post_node = self.document_network.nodes.get_mut(&post_node_id)?;
|
||||
new_node.inputs[new_node_input_index] = new_node_input;
|
||||
post_node.inputs[post_node_input_index] = post_node_input;
|
||||
|
||||
self.document_network.nodes.insert(id, node);
|
||||
self.document_network.nodes.insert(id, new_node);
|
||||
|
||||
self.shift_upstream(id, shift_upstream);
|
||||
self.shift_upstream(id, shift_upstream, false);
|
||||
|
||||
Some(id)
|
||||
}
|
||||
@@ -114,70 +123,113 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(new_id)
|
||||
}
|
||||
|
||||
pub fn skip_artboards(&self, output: &mut NodeOutput) -> Option<(NodeId, usize)> {
|
||||
while let NodeInput::Node { node_id, output_index, .. } = &self.document_network.nodes.get(&output.node_id)?.inputs[output.node_output_index] {
|
||||
let sibling_node = self.document_network.nodes.get(node_id)?;
|
||||
if sibling_node.name != "Artboard" {
|
||||
return Some((*node_id, *output_index));
|
||||
/// Starts at any folder, or the output, and skips layer nodes based on insert_index. Non layer nodes are always skipped. Returns the post node id, pre node id, and the input index.
|
||||
/// -----> Post node input_index: 0
|
||||
/// | if skip_layer_nodes == 0, return (Post node, Some(Layer1), 1)
|
||||
/// -> Layer1 input_index: 1
|
||||
/// ↑ if skip_layer_nodes == 1, return (Layer1, Some(Layer2), 0)
|
||||
/// -> Layer2 input_index: 2
|
||||
/// ↑
|
||||
/// -> NonLayerNode
|
||||
/// ↑ if skip_layer_nodes == 2, return (NonLayerNode, Some(Layer3), 0)
|
||||
/// -> Layer3 input_index: 3
|
||||
/// if skip_layer_nodes == 3, return (Layer3, None, 0)
|
||||
pub fn get_post_node_with_index(network: &NodeNetwork, mut post_node_id: NodeId, insert_index: usize) -> (NodeId, Option<NodeId>, usize) {
|
||||
let mut post_node_input_index = if post_node_id == NodeId(0) { 0 } else { 1 };
|
||||
|
||||
// Skip layers based on skip_layer_nodes, which inserts the new layer at a certain index of the layer stack.
|
||||
let mut current_index = 0;
|
||||
loop {
|
||||
if current_index == insert_index {
|
||||
break;
|
||||
}
|
||||
*output = NodeOutput::new(*node_id, *output_index)
|
||||
let next_node_in_stack_id = network
|
||||
.nodes
|
||||
.get(&post_node_id)
|
||||
.expect("Post node should always exist")
|
||||
.inputs
|
||||
.get(post_node_input_index)
|
||||
.and_then(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id.clone()) } else { None });
|
||||
|
||||
if let Some(next_node_in_stack_id) = next_node_in_stack_id {
|
||||
// Only increment index for layer nodes
|
||||
let next_node_in_stack = network.nodes.get(&next_node_in_stack_id).expect("Stack node should always exist");
|
||||
if next_node_in_stack.is_layer {
|
||||
current_index += 1;
|
||||
}
|
||||
|
||||
post_node_id = next_node_in_stack_id;
|
||||
|
||||
// Input as a sibling to the Layer node above
|
||||
post_node_input_index = 0;
|
||||
} else {
|
||||
log::error!("Error creating layer: insert_index out of bounds");
|
||||
break;
|
||||
};
|
||||
}
|
||||
None
|
||||
|
||||
// Move post_node to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at index 1 + insert_index
|
||||
let mut post_node = network.nodes.get(&post_node_id).expect("Post node should always exist");
|
||||
let mut pre_node_id = post_node
|
||||
.inputs
|
||||
.get(post_node_input_index)
|
||||
.and_then(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id.clone()) } else { None });
|
||||
|
||||
// Skip until pre_node is either a layer or does not exist
|
||||
while let Some(pre_node_id_value) = pre_node_id {
|
||||
let pre_node = network.nodes.get(&pre_node_id_value).expect("pre_node_id should be a layer");
|
||||
if !pre_node.is_layer {
|
||||
post_node = pre_node;
|
||||
post_node_id = pre_node_id_value;
|
||||
pre_node_id = post_node
|
||||
.inputs
|
||||
.get(0)
|
||||
.and_then(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id.clone()) } else { None });
|
||||
post_node_input_index = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(post_node_id, pre_node_id, post_node_input_index)
|
||||
}
|
||||
|
||||
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize, skip_layer_nodes: usize) -> Option<NodeId> {
|
||||
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, skip_layer_nodes: usize) -> Option<NodeId> {
|
||||
assert!(!self.document_network.nodes.contains_key(&new_id), "Creating already existing layer");
|
||||
|
||||
let mut output = NodeOutput::new(output_node_id, input_index);
|
||||
let mut sibling_layer = None;
|
||||
let mut shift = IVec2::new(0, 3);
|
||||
// Locate the node output of the first sibling layer to the new layer
|
||||
if let Some((node_id, output_index)) = self.skip_artboards(&mut output) {
|
||||
let sibling_node = self.document_network.nodes.get(&node_id)?;
|
||||
if sibling_node.is_layer() {
|
||||
// There is already a layer node
|
||||
sibling_layer = Some(NodeOutput::new(node_id, 0));
|
||||
} else {
|
||||
// The user has connected another node to the output. Insert a layer node between the output and the node.
|
||||
let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
|
||||
let node_id = self.insert_between(NodeId(generate_uuid()), NodeOutput::new(node_id, output_index), output, node, 0, 0, IVec2::new(-8, 0))?;
|
||||
sibling_layer = Some(NodeOutput::new(node_id, 0));
|
||||
}
|
||||
// Get the node which the new layer will output to (post node). First check if the output_node_id is the Output node, and set the output_node_id to the top-most artboard,
|
||||
// if there is one. Then skip layers based on skip_layer_nodes from the post_node.
|
||||
// TODO: Smarter placement of layers into artboards https://github.com/GraphiteEditor/Graphite/issues/1507
|
||||
|
||||
// Skip some layer nodes
|
||||
for _ in 0..skip_layer_nodes {
|
||||
if let Some(old_sibling) = &sibling_layer {
|
||||
output = NodeOutput::new(old_sibling.node_id, 1);
|
||||
sibling_layer = self.document_network.nodes.get(&old_sibling.node_id)?.inputs[1].as_node().map(|node| NodeOutput::new(node, 0));
|
||||
shift = IVec2::new(0, 3);
|
||||
let mut post_node_id = output_node_id;
|
||||
if post_node_id == NodeId(0) {
|
||||
// Check if an artboard is connected, and switch post node to the artboard.
|
||||
if let Some(NodeInput::Node { node_id, .. }) = &self.document_network.nodes.get(&post_node_id).expect("Output node should always exist").inputs.get(0) {
|
||||
let input_node = self.document_network.nodes.get(&node_id).expect("First input node should exist");
|
||||
if input_node.is_artboard() {
|
||||
post_node_id = *node_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert at top of stack
|
||||
}
|
||||
let (post_node_id, pre_node_id, post_node_input_index) = Self::get_post_node_with_index(self.document_network, post_node_id, skip_layer_nodes);
|
||||
let new_layer_node = resolve_document_node_type("Merge").expect("Merge node").default_document_node();
|
||||
if let Some(pre_node_id) = pre_node_id {
|
||||
self.insert_between(
|
||||
new_id,
|
||||
new_layer_node,
|
||||
NodeInput::node(pre_node_id, 0),
|
||||
0, // pre_node is a sibling so it connects to the first input
|
||||
post_node_id,
|
||||
NodeInput::node(new_id, 0),
|
||||
post_node_input_index,
|
||||
IVec2::new(0, 3),
|
||||
);
|
||||
} else {
|
||||
shift = IVec2::new(-8, 3);
|
||||
let offset = if post_node_input_index == 1 { IVec2::new(-8, 3) } else { IVec2::new(0, 3) };
|
||||
self.insert_node_before(new_id, post_node_id, post_node_input_index, new_layer_node, offset);
|
||||
}
|
||||
|
||||
// Create node
|
||||
let layer_node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
|
||||
let new_id = if let Some(sibling_layer) = sibling_layer {
|
||||
self.insert_between(new_id, sibling_layer, output, layer_node, 1, 0, shift)
|
||||
} else {
|
||||
self.insert_node_before(new_id, output.node_id, output.node_output_index, layer_node, shift)
|
||||
};
|
||||
|
||||
// Update the document metadata structure
|
||||
if let Some(new_id) = new_id {
|
||||
let parent = if self.document_network.nodes.get(&output_node_id).is_some_and(|node| node.is_layer()) {
|
||||
LayerNodeIdentifier::new(output_node_id, self.document_network)
|
||||
} else {
|
||||
LayerNodeIdentifier::ROOT
|
||||
};
|
||||
let new_child = LayerNodeIdentifier::new(new_id, self.document_network);
|
||||
parent.push_front_child(self.document_metadata, new_child);
|
||||
}
|
||||
|
||||
new_id
|
||||
Some(new_id)
|
||||
}
|
||||
|
||||
pub fn create_layer_with_insert_index(&mut self, new_id: NodeId, insert_index: isize, parent: LayerNodeIdentifier) -> Option<NodeId> {
|
||||
@@ -188,13 +240,17 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
} else {
|
||||
parent.to_node()
|
||||
};
|
||||
self.create_layer(new_id, output_node_id, 0, skip_layer_nodes)
|
||||
self.create_layer(new_id, output_node_id, skip_layer_nodes)
|
||||
}
|
||||
|
||||
pub fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
|
||||
/// Creates an artboard that outputs to the output node.
|
||||
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> Option<NodeId> {
|
||||
let output_node_id = self.document_network.original_outputs()[0].node_id;
|
||||
|
||||
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
|
||||
[
|
||||
None,
|
||||
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::EMPTY), true)),
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_core::GraphicGroup::EMPTY), true)),
|
||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
|
||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||
@@ -202,10 +258,35 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
],
|
||||
Default::default(),
|
||||
);
|
||||
self.responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
self.insert_node_before(NodeId(generate_uuid()), layer, 0, artboard_node, IVec2::new(-8, 0))
|
||||
}
|
||||
|
||||
// Get node that feeds into output. If it exists, connect the new artboard node in between. Else connect the new artboard directly to output.
|
||||
let output_node_primary_input = self.document_network.nodes.get(&output_node_id)?.inputs.get(0);
|
||||
let created_node_id = if let NodeInput::Node { node_id, .. } = &output_node_primary_input? {
|
||||
let pre_node = self.document_network.nodes.get(node_id)?;
|
||||
// If the node currently connected the Output is an artboard, connect to input 0 (Artboards input) of the new artboard. Else connect to the Over input.
|
||||
let artboard_input_index = if pre_node.is_artboard() { 0 } else { 1 };
|
||||
|
||||
self.insert_between(
|
||||
new_id,
|
||||
artboard_node,
|
||||
NodeInput::node(*node_id, 0),
|
||||
artboard_input_index,
|
||||
output_node_id,
|
||||
NodeInput::node(new_id, 0),
|
||||
0,
|
||||
IVec2::new(0, 3),
|
||||
)
|
||||
} else {
|
||||
self.insert_node_before(new_id, output_node_id, 0, artboard_node, IVec2::new(-8, 3))
|
||||
};
|
||||
|
||||
if let Some(new_id) = created_node_id {
|
||||
let new_child = LayerNodeIdentifier::new_unchecked(new_id);
|
||||
LayerNodeIdentifier::ROOT.push_front_child(self.document_metadata, new_child);
|
||||
}
|
||||
|
||||
created_node_id
|
||||
}
|
||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
|
||||
let shape = {
|
||||
let node_type = resolve_document_node_type("Shape").expect("Shape node does not exist");
|
||||
@@ -216,7 +297,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node();
|
||||
|
||||
let stroke_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(stroke_id, layer, 0, stroke, IVec2::new(-8, 0));
|
||||
self.insert_node_before(stroke_id, layer, 1, stroke, IVec2::new(-8, 0));
|
||||
let fill_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0));
|
||||
let transform_id = NodeId(generate_uuid());
|
||||
@@ -241,7 +322,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_document_node();
|
||||
|
||||
let stroke_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(stroke_id, layer, 0, stroke, IVec2::new(-8, 0));
|
||||
self.insert_node_before(stroke_id, layer, 1, stroke, IVec2::new(-8, 0));
|
||||
let fill_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(fill_id, stroke_id, 0, fill, IVec2::new(-8, 0));
|
||||
let transform_id = NodeId(generate_uuid());
|
||||
@@ -259,7 +340,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_document_node();
|
||||
|
||||
let transform_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(transform_id, layer, 0, transform, IVec2::new(-8, 0));
|
||||
self.insert_node_before(transform_id, layer, 1, transform, IVec2::new(-8, 0));
|
||||
|
||||
let image_id = NodeId(generate_uuid());
|
||||
self.insert_node_before(image_id, transform_id, 0, image, IVec2::new(-8, 0));
|
||||
@@ -267,8 +348,12 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
self.responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
|
||||
pub fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2, shift_self: bool) {
|
||||
let mut shift_nodes = HashSet::new();
|
||||
if shift_self {
|
||||
shift_nodes.insert(node_id);
|
||||
}
|
||||
|
||||
let mut stack = vec![node_id];
|
||||
while let Some(node_id) = stack.pop() {
|
||||
let Some(node) = self.document_network.nodes.get(&node_id) else { continue };
|
||||
@@ -295,11 +380,12 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
return;
|
||||
};
|
||||
|
||||
let input_index = if output_node.is_layer { 1 } else { 0 };
|
||||
let metadata = output_node.metadata.clone();
|
||||
let new_input = output_node.inputs.first().cloned().filter(|input| input.as_node().is_some());
|
||||
let new_input = output_node.inputs.get(input_index).cloned().filter(|input| input.as_node().is_some());
|
||||
let node_id = NodeId(generate_uuid());
|
||||
|
||||
output_node.inputs[0] = NodeInput::node(node_id, 0);
|
||||
output_node.inputs[input_index] = NodeInput::node(node_id, 0);
|
||||
|
||||
let Some(node_type) = resolve_document_node_type(name) else {
|
||||
warn!("Node type \"{name}\" doesn't exist");
|
||||
@@ -309,7 +395,11 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
update_input(&mut new_document_node.inputs, node_id, self.document_metadata);
|
||||
self.document_network.nodes.insert(node_id, new_document_node);
|
||||
|
||||
let upstream_nodes = self.document_network.upstream_flow_back_from_nodes(vec![node_id], true).map(|(_, id)| id).collect::<Vec<_>>();
|
||||
let upstream_nodes = self
|
||||
.document_network
|
||||
.upstream_flow_back_from_nodes(vec![node_id], graph_craft::document::FlowType::HorizontalFlow)
|
||||
.map(|(_, id)| id)
|
||||
.collect::<Vec<_>>();
|
||||
for node_id in upstream_nodes {
|
||||
let Some(node) = self.document_network.nodes.get_mut(&node_id) else { continue };
|
||||
node.metadata.position.x -= 8;
|
||||
@@ -323,7 +413,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
.upstream_flow_back_from_nodes(
|
||||
self.layer_node
|
||||
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]),
|
||||
true,
|
||||
graph_craft::document::FlowType::HorizontalFlow,
|
||||
)
|
||||
.find(|(node, _)| node.name == name)
|
||||
.map(|(_, id)| id);
|
||||
@@ -348,7 +438,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
.upstream_flow_back_from_nodes(
|
||||
self.layer_node
|
||||
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]),
|
||||
true,
|
||||
graph_craft::document::FlowType::HorizontalFlow,
|
||||
)
|
||||
.filter(|(node, _)| node.name == name)
|
||||
.map(|(_, id)| id)
|
||||
@@ -525,159 +615,20 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
|
||||
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
|
||||
self.modify_inputs("Artboard", false, |inputs, _node_id, _metadata| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
|
||||
let mut dimensions = dimensions;
|
||||
let mut location = location;
|
||||
|
||||
if dimensions.x < 0 {
|
||||
dimensions.x *= -1;
|
||||
location.x += dimensions.x;
|
||||
}
|
||||
if dimensions.y < 0 {
|
||||
dimensions.y *= -1;
|
||||
location.y += dimensions.y;
|
||||
}
|
||||
|
||||
inputs[2] = NodeInput::value(TaggedValue::IVec2(location), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn delete_layer(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes, is_artboard_layer: bool) {
|
||||
let Some(node) = self.document_network.nodes.get(&id) else {
|
||||
warn!("Deleting layer node that does not exist");
|
||||
return;
|
||||
};
|
||||
|
||||
let layer_node = LayerNodeIdentifier::new(id, self.document_network);
|
||||
let child_layers = layer_node.descendants(self.document_metadata).map(|layer| layer.to_node()).collect::<Vec<_>>();
|
||||
layer_node.delete(self.document_metadata);
|
||||
|
||||
// An artboard layer is a layer node to which an artboard node was connected.
|
||||
// However, since this method is called after `delete_artboard`, the artboard node is already deleted.
|
||||
// So, instead of a single ordinary node, we have a stack of layers connected to the current artboard layer through `node_inputs[0]` (instead of `node_inputs[1]`).
|
||||
let is_artboard_layer = if is_artboard_layer && matches!(&node.inputs[0], NodeInput::Value { .. }) {
|
||||
false
|
||||
} else {
|
||||
is_artboard_layer
|
||||
};
|
||||
|
||||
// For an artboard layer, the new input is the top of the stack of layers that is connected to it through `node_inputs[0]`.
|
||||
// For an ordinary layer, the new input is the next layer in the current stack of layers, which is connected to it through `node_inputs[1]`.
|
||||
let new_input = if is_artboard_layer && !matches!(&node.inputs[0], NodeInput::Value { .. }) {
|
||||
node.inputs[0].clone()
|
||||
} else {
|
||||
node.inputs[1].clone()
|
||||
};
|
||||
let deleted_position = node.metadata.position;
|
||||
|
||||
if let Some(new_input_id) = is_artboard_layer.then(|| new_input.as_node()).flatten() {
|
||||
// This is the artboard layer that will be connected to the bottom of the stack of layers that is connected to the current artboard layer to be deleted.
|
||||
// This will move the stack into the "main stack" of layers that leads to the output.
|
||||
let new_input_artboard_layer = node.inputs[1].clone();
|
||||
|
||||
// Find the last layer node in the stack of layers that is connected to the current artboard layer to be deleted.
|
||||
let mut final_layer_node_id = new_input_id;
|
||||
|
||||
let nodes = &self.document_network.nodes;
|
||||
while let Some(input_id) = nodes.get(&final_layer_node_id).and_then(|input_node| input_node.inputs.get(1).and_then(|x| x.as_node())) {
|
||||
final_layer_node_id = input_id;
|
||||
}
|
||||
|
||||
// Connect `new_input_artboard_layer` to `final_layer_node`
|
||||
if let Some(final_layer_node) = self.document_network.nodes.get_mut(&final_layer_node_id) {
|
||||
final_layer_node.inputs[1] = new_input_artboard_layer.clone();
|
||||
}
|
||||
|
||||
// Shift the position of the stack of layers connected to `new_input_artboard_layer` to the bottom of `final_layer_node`
|
||||
if let Some(final_layer_node) = self.document_network.nodes.get(&final_layer_node_id) {
|
||||
if let Some(new_input_artboard_layer_id) = new_input_artboard_layer.as_node() {
|
||||
if let Some(new_input_artboard_layer_node) = self.document_network.nodes.get(&new_input_artboard_layer_id) {
|
||||
let shift = final_layer_node.metadata.position - new_input_artboard_layer_node.metadata.position + IVec2::new(0, 3);
|
||||
|
||||
let node_ids = self
|
||||
.document_network
|
||||
.upstream_flow_back_from_nodes(vec![new_input_artboard_layer_id], false)
|
||||
.map(|(_, id)| id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for node_id in node_ids {
|
||||
let Some(node) = self.document_network.nodes.get_mut(&node_id) else { continue };
|
||||
node.metadata.position += shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all nodes that the layer to be deleted is connected to
|
||||
for post_node in self.outwards_links.get(&id).unwrap_or(&Vec::new()) {
|
||||
let Some(node) = self.document_network.nodes.get_mut(post_node) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Update the inputs of these nodes by replacing the layer to be deleted with `new_input`
|
||||
for input in &mut node.inputs {
|
||||
if let NodeInput::Node { node_id, .. } = input {
|
||||
if *node_id == id {
|
||||
*input = new_input.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut delete_nodes = vec![id];
|
||||
for (_node, id) in self.document_network.upstream_flow_back_from_nodes([vec![id], child_layers].concat(), true) {
|
||||
// Don't delete the node if it's an artboard layer or if other layers depend on it.
|
||||
if is_artboard_layer || self.outwards_links.get(&id).is_some_and(|nodes| nodes.len() > 1) {
|
||||
break;
|
||||
}
|
||||
// Delete the node if it is connected to only the current layer
|
||||
if self.outwards_links.get(&id).is_some_and(|outwards| outwards.len() == 1) {
|
||||
delete_nodes.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
for node_id in &delete_nodes {
|
||||
self.document_network.nodes.remove(node_id);
|
||||
}
|
||||
|
||||
// Shift the position of the nodes that are connected to the deleted nodes
|
||||
if let Some(node_id) = new_input.as_node() {
|
||||
if let Some(shift) = self.document_network.nodes.get(&node_id).map(|node| deleted_position - node.metadata.position) {
|
||||
for node_id in self.document_network.upstream_flow_back_from_nodes(vec![node_id], false).map(|(_, id)| id).collect::<Vec<_>>() {
|
||||
let Some(node) = self.document_network.nodes.get_mut(&node_id) else { continue };
|
||||
node.metadata.position += shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selected_nodes.retain_selected_nodes(|id| !delete_nodes.contains(id));
|
||||
|
||||
// Update the outwards links
|
||||
self.outwards_links = self.document_network.collect_outwards_links();
|
||||
self.responses.add(BroadcastEvent::SelectionChanged);
|
||||
self.responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn delete_artboard(&mut self, id: NodeId, selected_nodes: &mut SelectedNodes) {
|
||||
let Some(node) = self.document_network.nodes.get(&id) else {
|
||||
warn!("Deleting artboard node that does not exist");
|
||||
return;
|
||||
};
|
||||
|
||||
let new_input = node.inputs[0].clone();
|
||||
|
||||
// Get all nodes that the artboard is connected to
|
||||
for post_node in self.outwards_links.get(&id).unwrap_or(&Vec::new()) {
|
||||
let Some(node) = self.document_network.nodes.get_mut(post_node) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Update the inputs of these nodes by replacing the artboard with `new_input`
|
||||
for input in &mut node.inputs {
|
||||
if let NodeInput::Node { node_id, .. } = input {
|
||||
if *node_id == id {
|
||||
*input = new_input.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the artboard node
|
||||
self.document_network.nodes.remove(&id);
|
||||
selected_nodes.retain_selected_nodes(|&node_id| id != node_id);
|
||||
|
||||
// Update the outwards links
|
||||
self.outwards_links = self.document_network.collect_outwards_links();
|
||||
self.responses.add(BroadcastEvent::SelectionChanged);
|
||||
self.responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@ pub struct NodePropertiesContext<'a> {
|
||||
pub struct DocumentNodeDefinition {
|
||||
pub name: &'static str,
|
||||
pub category: &'static str,
|
||||
pub is_layer: bool,
|
||||
pub implementation: DocumentNodeImplementation,
|
||||
pub inputs: Vec<DocumentInputType>,
|
||||
pub outputs: Vec<DocumentOutputType>,
|
||||
@@ -94,6 +95,7 @@ impl Default for DocumentNodeDefinition {
|
||||
Self {
|
||||
name: Default::default(),
|
||||
category: Default::default(),
|
||||
is_layer: false,
|
||||
implementation: Default::default(),
|
||||
inputs: Default::default(),
|
||||
outputs: Default::default(),
|
||||
@@ -191,10 +193,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Layer",
|
||||
name: "Merge",
|
||||
category: "General",
|
||||
is_layer: true,
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
imports: vec![NodeId(0), NodeId(2)],
|
||||
imports: vec![NodeId(2), NodeId(0)],
|
||||
exports: vec![NodeOutput::new(NodeId(2), 0)],
|
||||
nodes: [
|
||||
(
|
||||
@@ -207,6 +210,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
),
|
||||
// The monitor node is used to display a thumbnail in the UI.
|
||||
// TODO: Check if thumbnail is reversed
|
||||
(
|
||||
NodeId(1),
|
||||
DocumentNode {
|
||||
@@ -233,18 +237,64 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Graphical Data", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
DocumentInputType::value("Stack", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
DocumentInputType::value("Over", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::GraphicGroup)],
|
||||
properties: node_properties::layer_no_properties,
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
name: "Artboard",
|
||||
category: "General",
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _>"),
|
||||
is_layer: true,
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
imports: vec![NodeId(2), NodeId(0), NodeId(0), NodeId(0), NodeId(0), NodeId(0)],
|
||||
exports: vec![NodeOutput::new(NodeId(2), 0)],
|
||||
nodes: [
|
||||
(
|
||||
NodeId(0),
|
||||
DocumentNode {
|
||||
name: "To Artboard".to_string(),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![
|
||||
NodeInput::Network(concrete!(graphene_core::GraphicGroup)),
|
||||
NodeInput::Network(concrete!(TaggedValue)),
|
||||
NodeInput::Network(concrete!(TaggedValue)),
|
||||
NodeInput::Network(concrete!(TaggedValue)),
|
||||
NodeInput::Network(concrete!(TaggedValue)),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _>"),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
// The monitor node is used to display a thumbnail in the UI.
|
||||
// TODO: Check if thumbnail is reversed
|
||||
(
|
||||
NodeId(1),
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
..monitor_node()
|
||||
},
|
||||
),
|
||||
(
|
||||
NodeId(2),
|
||||
DocumentNode {
|
||||
name: "Add to Artboards".to_string(),
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
inputs: vec![
|
||||
NodeInput::node(NodeId(1), 0),
|
||||
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(concrete!(ArtboardGroup)))),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::AddArtboardNode<_, _>"),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
DocumentInputType::value("Artboards", TaggedValue::ArtboardGroup(ArtboardGroup::EMPTY), true),
|
||||
DocumentInputType::value("Over", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true),
|
||||
DocumentInputType::value("Location", TaggedValue::IVec2(glam::IVec2::ZERO), false),
|
||||
DocumentInputType::value("Dimensions", TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
|
||||
DocumentInputType::value("Background", TaggedValue::Color(Color::WHITE), false),
|
||||
@@ -252,7 +302,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
|
||||
properties: node_properties::artboard_properties,
|
||||
manual_composition: Some(concrete!(Footprint)),
|
||||
..Default::default()
|
||||
},
|
||||
// TODO: Does this need an internal Cull node to be added to its implementation?
|
||||
@@ -2936,6 +2985,7 @@ impl DocumentNodeDefinition {
|
||||
|
||||
DocumentNode {
|
||||
name: self.name.to_string(),
|
||||
is_layer: self.is_layer,
|
||||
inputs,
|
||||
manual_composition: self.manual_composition.clone(),
|
||||
has_primary_output: self.has_primary_output,
|
||||
@@ -2953,7 +3003,7 @@ impl DocumentNodeDefinition {
|
||||
self.to_document_node(inputs, metadata)
|
||||
}
|
||||
|
||||
/// Converts the [DocumentNodeDefinition] type to a [DocumentNode], completely default
|
||||
/// Converts the [DocumentNodeDefinition] type to a [DocumentNode], completely default.
|
||||
pub fn default_document_node(&self) -> DocumentNode {
|
||||
self.to_document_node(self.inputs.iter().map(|input| input.default.clone()), DocumentNodeMetadata::default())
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ pub enum NodeGraphMessage {
|
||||
y: i32,
|
||||
},
|
||||
Cut,
|
||||
DeleteNode {
|
||||
node_id: NodeId,
|
||||
DeleteNodes {
|
||||
node_ids: Vec<NodeId>,
|
||||
reconnect: bool,
|
||||
},
|
||||
DeleteSelectedNodes {
|
||||
@@ -40,6 +40,9 @@ pub enum NodeGraphMessage {
|
||||
node: NodeId,
|
||||
},
|
||||
DuplicateSelectedNodes,
|
||||
EnforceLayerHasNoMultiParams {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ExitNestedNetwork {
|
||||
depth_of_nesting: usize,
|
||||
},
|
||||
@@ -52,6 +55,15 @@ pub enum NodeGraphMessage {
|
||||
node_id: NodeId,
|
||||
document_node: DocumentNode,
|
||||
},
|
||||
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,
|
||||
},
|
||||
MoveSelectedNodes {
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
@@ -89,18 +101,10 @@ pub enum NodeGraphMessage {
|
||||
ShiftNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ToggleSelectedVisibility,
|
||||
ToggleVisibility {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetVisibility {
|
||||
node_id: NodeId,
|
||||
visible: bool,
|
||||
},
|
||||
ToggleSelectedLocked,
|
||||
ToggleLocked {
|
||||
node_id: NodeId,
|
||||
},
|
||||
SetLocked {
|
||||
node_id: NodeId,
|
||||
locked: bool,
|
||||
@@ -113,12 +117,25 @@ pub enum NodeGraphMessage {
|
||||
node_id: NodeId,
|
||||
name: String,
|
||||
},
|
||||
SetToNodeOrLayer {
|
||||
node_id: NodeId,
|
||||
is_layer: bool,
|
||||
},
|
||||
ToggleLocked {
|
||||
node_id: NodeId,
|
||||
},
|
||||
TogglePreview {
|
||||
node_id: NodeId,
|
||||
},
|
||||
TogglePreviewImpl {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ToggleSelectedLayers,
|
||||
ToggleSelectedLocked,
|
||||
ToggleSelectedVisibility,
|
||||
ToggleVisibility {
|
||||
node_id: NodeId,
|
||||
},
|
||||
UpdateNewNodeGraph,
|
||||
UpdateTypes {
|
||||
#[serde(skip)]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput, Source};
|
||||
use graph_craft::document::{DocumentNode, FlowType, NodeId, NodeInput, NodeNetwork, NodeOutput, Source};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_core::*;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
@@ -11,7 +11,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::graph_operation::load_network_structure;
|
||||
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::nodes::{CollapsedLayers, LayerClassification, LayerPanelEntry, SelectedNodes};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use glam::IVec2;
|
||||
@@ -66,6 +66,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
new_layer: selected_nodes.selected_layers(document_metadata).next(),
|
||||
});
|
||||
}
|
||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
NodeGraphMessage::ConnectNodesByLink {
|
||||
@@ -139,14 +140,83 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(NodeGraphMessage::Copy);
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
}
|
||||
NodeGraphMessage::DeleteNode { node_id, reconnect } => {
|
||||
self.remove_node(document_network, selected_nodes, node_id, responses, reconnect);
|
||||
}
|
||||
NodeGraphMessage::DeleteSelectedNodes { reconnect } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
NodeGraphMessage::DeleteNodes { node_ids, reconnect } => {
|
||||
let mut delete_nodes = HashSet::new();
|
||||
|
||||
for node_id in selected_nodes.selected_nodes().copied() {
|
||||
responses.add(NodeGraphMessage::DeleteNode { node_id, reconnect });
|
||||
for node_id in &node_ids {
|
||||
delete_nodes.insert(*node_id);
|
||||
|
||||
if !reconnect {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(node) = document_network.nodes.get(&node_id) else {
|
||||
continue;
|
||||
};
|
||||
let child_id = node.inputs.get(1).and_then(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None });
|
||||
let Some(child_id) = child_id else {
|
||||
continue;
|
||||
};
|
||||
|
||||
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) {
|
||||
// TODO: move into a document_network function .is_sole_dependent. This function does a downstream traversal starting from the current node,
|
||||
// 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,
|
||||
// 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 can_delete = true;
|
||||
|
||||
// TODO: Add iteration limit to force break in case of infinite while loop
|
||||
while let Some(current_node) = stack.pop() {
|
||||
if let Some(downstream_nodes) = outward_links.get(¤t_node) {
|
||||
for downstream_node in downstream_nodes {
|
||||
if document_network.original_outputs_contain(*downstream_node) {
|
||||
can_delete = false;
|
||||
} else if !delete_nodes.contains(downstream_node) {
|
||||
stack.push(*downstream_node);
|
||||
}
|
||||
// Continue traversing over the downstream sibling, which happens if the current node is a sibling to a node in node_ids
|
||||
else {
|
||||
for deleted_node_id in &node_ids {
|
||||
let Some(output_node) = document_network.nodes.get(&deleted_node_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(input) = output_node.inputs.get(0) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let NodeInput::Node { node_id, .. } = input {
|
||||
if *node_id == current_node {
|
||||
stack.push(*deleted_node_id);
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if can_delete {
|
||||
delete_nodes.insert(upstream_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for delete_node_id in delete_nodes {
|
||||
let Some(delete_node) = document_network.nodes.get(&delete_node_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if delete_node.is_layer {
|
||||
// Delete node from document metadata
|
||||
let layer_node = LayerNodeIdentifier::new(delete_node_id, document_network);
|
||||
layer_node.delete(document_metadata);
|
||||
}
|
||||
|
||||
self.remove_node(document_network, selected_nodes, delete_node_id, responses, reconnect);
|
||||
}
|
||||
|
||||
if let Some(network) = document_network.nested_network(&self.network) {
|
||||
@@ -155,6 +225,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
// There is no need to call `load_network_structure()` since the metadata is already updated
|
||||
}
|
||||
// Deletes selected_nodes. If `reconnect` is true, then all children nodes (secondary input) of the selected nodes are deleted and the siblings (primary input/output) are reconnected.
|
||||
// If `reconnect` is false, then only the selected nodes are deleted and not reconnected.
|
||||
NodeGraphMessage::DeleteSelectedNodes { reconnect } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: selected_nodes.selected_nodes().copied().collect(),
|
||||
reconnect,
|
||||
});
|
||||
}
|
||||
NodeGraphMessage::DisconnectNodes { node_id, input_index } => {
|
||||
let Some(network) = document_network.nested_network(&self.network) else {
|
||||
@@ -169,16 +250,16 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
warn!("Node {} not in library", node.name);
|
||||
return;
|
||||
};
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
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) {
|
||||
@@ -224,6 +305,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
self.update_selected(document_network, document_metadata, selected_nodes, responses);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::EnforceLayerHasNoMultiParams { node_id } => {
|
||||
if !self.eligible_to_be_layer(document_network, node_id) {
|
||||
responses.add(NodeGraphMessage::SetToNodeOrLayer { node_id: node_id, is_layer: false })
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
|
||||
selected_nodes.clear_selected_nodes();
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
@@ -260,8 +346,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
};
|
||||
}
|
||||
}
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
responses.add(NodeGraphMessage::EnforceLayerHasNoMultiParams { node_id });
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
@@ -270,6 +357,57 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
network.nodes.insert(node_id, document_node);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::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(network) = document_network.nested_network(&self.network) else {
|
||||
error!("No network");
|
||||
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);
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
if network.connected_to_output(insert_node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => {
|
||||
let Some(network) = document_network.nested_network_mut(&self.network) else {
|
||||
warn!("No network");
|
||||
@@ -281,7 +419,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
node.metadata.position += IVec2::new(displacement_x, displacement_y)
|
||||
}
|
||||
}
|
||||
self.send_graph(network, graph_view_overlay_open, document_metadata, selected_nodes, collapsed, responses);
|
||||
|
||||
// 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::PasteNodes { serialized_nodes } => {
|
||||
let Some(network) = document_network.nested_network(&self.network) else {
|
||||
@@ -523,6 +667,37 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
document_metadata.load_structure(document_network, selected_nodes);
|
||||
self.update_selection_action_buttons(document_network, document_metadata, selected_nodes, responses);
|
||||
}
|
||||
NodeGraphMessage::ToggleSelectedLayers => {
|
||||
let Some(network) = document_network.nested_network_mut(&self.network) else { return };
|
||||
|
||||
for node_id in selected_nodes.selected_nodes() {
|
||||
let Some(node) = network.nodes.get_mut(&node_id) else { continue };
|
||||
|
||||
if node.has_primary_output {
|
||||
responses.add(NodeGraphMessage::SetToNodeOrLayer {
|
||||
node_id: *node_id,
|
||||
is_layer: !node.is_layer,
|
||||
});
|
||||
}
|
||||
|
||||
if network.connected_to_output(*node_id) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||
if is_layer && !self.eligible_to_be_layer(document_network, node_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(network) = document_network.nested_network_mut(&self.network) else { return };
|
||||
|
||||
if let Some(node) = network.nodes.get_mut(&node_id) {
|
||||
node.is_layer = is_layer;
|
||||
}
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
}
|
||||
NodeGraphMessage::SetName { node_id, name } => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(NodeGraphMessage::SetNameImpl { node_id, name });
|
||||
@@ -585,7 +760,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
impl NodeGraphMessageHandler {
|
||||
pub fn actions_with_node_graph_open(&self, graph_open: bool) -> ActionList {
|
||||
if self.has_selection && graph_open {
|
||||
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, ToggleSelectedLocked, DuplicateSelectedNodes, DeleteSelectedNodes, Cut, Copy)
|
||||
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, ToggleSelectedLocked, ToggleSelectedLayers, DuplicateSelectedNodes, DeleteSelectedNodes, Cut, Copy)
|
||||
} else if self.has_selection {
|
||||
actions!(NodeGraphMessageDiscriminant; ToggleSelectedVisibility, ToggleSelectedLocked)
|
||||
} else {
|
||||
@@ -669,7 +844,7 @@ impl NodeGraphMessageHandler {
|
||||
let (mut layers, mut nodes) = (Vec::new(), Vec::new());
|
||||
for node_id in selected_nodes.selected_nodes() {
|
||||
if let Some(layer_or_node) = network.nodes.get(node_id) {
|
||||
if layer_or_node.is_layer() {
|
||||
if layer_or_node.is_layer {
|
||||
layers.push(*node_id);
|
||||
} else {
|
||||
nodes.push(*node_id);
|
||||
@@ -688,16 +863,16 @@ impl NodeGraphMessageHandler {
|
||||
1 => {
|
||||
let nodes_not_upstream_of_layer = nodes
|
||||
.into_iter()
|
||||
.filter(|&selected_node_id| !network.is_node_upstream_of_another_by_primary_flow(layers[0], selected_node_id));
|
||||
.filter(|&selected_node_id| !network.is_node_upstream_of_another_by_horizontal_flow(layers[0], selected_node_id));
|
||||
if nodes_not_upstream_of_layer.count() > 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
// Iterate through all the upstream nodes, but stop when we reach another layer (since that's a point where we switch from horizontal to vertical flow)
|
||||
network
|
||||
.upstream_flow_back_from_nodes(vec![layers[0]], true)
|
||||
.upstream_flow_back_from_nodes(vec![layers[0]], graph_craft::document::FlowType::HorizontalFlow)
|
||||
.enumerate()
|
||||
.take_while(|(i, (node, _))| if *i == 0 { true } else { !node.is_layer() })
|
||||
.take_while(|(i, (node, _))| if *i == 0 { true } else { !node.is_layer })
|
||||
.map(|(_, (node, node_id))| node_properties::generate_node_properties(node, node_id, context))
|
||||
.collect()
|
||||
}
|
||||
@@ -733,7 +908,10 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
|
||||
fn collect_nodes(&self, links: &[FrontendNodeLink], network: &NodeNetwork) -> Vec<FrontendNode> {
|
||||
let connected_node_to_output_lookup = links.iter().map(|link| ((link.link_start, link.link_start_output_index), link.link_end)).collect::<HashMap<_, _>>();
|
||||
let connected_node_to_output_lookup = links
|
||||
.iter()
|
||||
.map(|link| ((link.link_start, link.link_start_output_index), (link.link_end, link.link_end_input_index)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for (&node_id, node) in &network.nodes {
|
||||
@@ -769,22 +947,27 @@ impl NodeGraphMessageHandler {
|
||||
let exposed_inputs = inputs.filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type).collect();
|
||||
|
||||
// Outputs
|
||||
let mut outputs = document_node_definition.outputs.iter().enumerate().map(|(index, output_type)| FrontendGraphOutput {
|
||||
data_type: output_type.data_type,
|
||||
name: output_type.name.to_string(),
|
||||
resolved_type: self.resolved_types.outputs.get(&Source { node: node_path.clone(), index }).map(|output| format!("{output:?}")),
|
||||
connected: connected_node_to_output_lookup.get(&(node_id, index)).copied(),
|
||||
let mut outputs = document_node_definition.outputs.iter().enumerate().map(|(index, output_type)| {
|
||||
let (connected, connected_index) = connected_node_to_output_lookup.get(&(node_id, index)).copied().map(|(a, b)| (Some(a), Some(b))).unwrap_or((None, None));
|
||||
|
||||
FrontendGraphOutput {
|
||||
data_type: output_type.data_type,
|
||||
name: output_type.name.to_string(),
|
||||
resolved_type: self.resolved_types.outputs.get(&Source { node: node_path.clone(), index }).map(|output| format!("{output:?}")),
|
||||
connected,
|
||||
connected_index,
|
||||
}
|
||||
});
|
||||
let primary_output = node.has_primary_output.then(|| outputs.next()).flatten();
|
||||
let exposed_outputs = outputs.collect::<Vec<_>>();
|
||||
|
||||
// Errors
|
||||
let errors = self.node_graph_errors.iter().find(|error| error.node_path.starts_with(&node_path)).map(|error| error.error.clone());
|
||||
|
||||
nodes.push(FrontendNode {
|
||||
id: node_id,
|
||||
is_layer: node.is_layer(),
|
||||
alias: node.alias.clone(),
|
||||
is_layer: node.is_layer,
|
||||
can_be_layer: self.eligible_to_be_layer(network, node_id),
|
||||
alias: Self::untitled_layer_label(node),
|
||||
name: node.name.clone(),
|
||||
primary_input,
|
||||
exposed_inputs,
|
||||
@@ -802,17 +985,8 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
fn update_layer_panel(network: &NodeNetwork, metadata: &DocumentMetadata, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
|
||||
for (&node_id, node) in &network.nodes {
|
||||
if node.is_layer() {
|
||||
if node.is_layer {
|
||||
let layer = LayerNodeIdentifier::new(node_id, network);
|
||||
let layer_classification = {
|
||||
if metadata.is_artboard(layer) {
|
||||
LayerClassification::Artboard
|
||||
} else if metadata.is_folder(layer) {
|
||||
LayerClassification::Folder
|
||||
} else {
|
||||
LayerClassification::Layer
|
||||
}
|
||||
};
|
||||
|
||||
let parents_visible = layer
|
||||
.ancestors(metadata)
|
||||
@@ -826,12 +1000,21 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
let data = LayerPanelEntry {
|
||||
id: node_id,
|
||||
layer_classification,
|
||||
children_allowed:
|
||||
// The layer has other layers as children along the secondary input's horizontal flow
|
||||
layer.has_children(metadata)
|
||||
|| (
|
||||
// At least one secondary input is exposed on this layer node
|
||||
node.inputs.iter().skip(1).any(|input| input.is_exposed()) &&
|
||||
// But nothing is connected to it, since we only get 1 item (ourself) when we ask for the flow from the secondary input
|
||||
network.upstream_flow_back_from_nodes(vec![node_id], FlowType::HorizontalFlow).count() == 1
|
||||
),
|
||||
children_present: layer.has_children(metadata),
|
||||
expanded: layer.has_children(metadata) && !collapsed.0.contains(&layer),
|
||||
has_children: layer.has_children(metadata),
|
||||
depth: layer.ancestors(metadata).count() - 1,
|
||||
parent_id: layer.parent(metadata).map(|parent| parent.to_node()),
|
||||
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
|
||||
name: node.name.clone(),
|
||||
alias: Self::untitled_layer_label(node),
|
||||
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
|
||||
visible: node.visible,
|
||||
parents_visible,
|
||||
@@ -844,10 +1027,10 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
|
||||
fn send_graph(&self, network: &NodeNetwork, graph_open: bool, metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
|
||||
metadata.load_structure(network, selected_nodes);
|
||||
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
// TODO: Move update_layer_panel into message so load structure here can be removed, since load structure is already called in DocumentStructureChanged
|
||||
metadata.load_structure(network, selected_nodes);
|
||||
Self::update_layer_panel(network, metadata, collapsed, responses);
|
||||
if graph_open {
|
||||
let links = Self::collect_links(network);
|
||||
@@ -880,9 +1063,8 @@ impl NodeGraphMessageHandler {
|
||||
// Check whether the being-deleted node's first (primary) input is a node
|
||||
if let Some(node) = network.nodes.get(&deleting_node_id) {
|
||||
// Reconnect to the node below when deleting a layer node.
|
||||
let reconnect_from_input_index = if node.is_layer() { 1 } else { 0 };
|
||||
if matches!(&node.inputs.get(reconnect_from_input_index), Some(NodeInput::Node { .. })) {
|
||||
reconnect_to_input = Some(node.inputs[reconnect_from_input_index].clone());
|
||||
if matches!(&node.inputs.get(0), Some(NodeInput::Node { .. })) {
|
||||
reconnect_to_input = Some(node.inputs[0].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,6 +1141,26 @@ impl NodeGraphMessageHandler {
|
||||
.filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone())))
|
||||
.map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids)))
|
||||
}
|
||||
|
||||
pub fn eligible_to_be_layer(&self, document_network: &NodeNetwork, node_id: NodeId) -> bool {
|
||||
let Some(network) = document_network.nested_network(&self.network) else { return false };
|
||||
let Some(node) = network.nodes.get(&node_id) else { return false };
|
||||
let Some(definition) = resolve_document_node_type(&node.name) else { return false };
|
||||
|
||||
let exposed_value_count = node.inputs.iter().filter(|input| if let NodeInput::Value { exposed, .. } = input { *exposed } else { false }).count();
|
||||
let node_input_count = node.inputs.iter().filter(|input| if let NodeInput::Node { .. } = input { true } else { false }).count();
|
||||
let input_count = node_input_count + exposed_value_count;
|
||||
let output_count = definition.outputs.len();
|
||||
|
||||
// TODO: Eventually allow nodes at the bottom of a stack to be layers, where `input_count` is 0
|
||||
node.has_primary_output && output_count == 1 && (input_count == 1 || input_count == 2)
|
||||
}
|
||||
|
||||
fn untitled_layer_label(node: &DocumentNode) -> String {
|
||||
(node.alias != "")
|
||||
.then_some(node.alias.clone())
|
||||
.unwrap_or(if node.is_layer && node.name == "Merge" { "Untitled Layer".to_string() } else { node.name.clone() })
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NodeGraphMessageHandler {
|
||||
|
||||
@@ -2236,12 +2236,8 @@ fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
|
||||
string_properties(format!("Node '{}' cannot be found in library", document_node.name))
|
||||
}
|
||||
|
||||
pub fn node_no_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
string_properties("Node has no properties")
|
||||
}
|
||||
|
||||
pub fn layer_no_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
string_properties("Layer has no properties")
|
||||
pub fn node_no_properties(document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
string_properties(if document_node.is_layer { "Layer has no properties" } else { "Node has no properties" })
|
||||
}
|
||||
|
||||
pub fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
@@ -2423,12 +2419,11 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
}
|
||||
|
||||
pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", " px", None, add_blank_assist);
|
||||
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", " px", None, add_blank_assist);
|
||||
let background = color_widget(document_node, node_id, 3, "Background", ColorButton::default().allow_none(false), true);
|
||||
let clip = LayoutGroup::Row {
|
||||
widgets: bool_widget(document_node, node_id, 4, "Clip", true),
|
||||
};
|
||||
let location = vec2_widget(document_node, node_id, 2, "Location", "X", "Y", " px", None, add_blank_assist);
|
||||
let dimensions = vec2_widget(document_node, node_id, 3, "Dimensions", "W", "H", " px", None, add_blank_assist);
|
||||
let background = color_widget(document_node, node_id, 4, "Background", ColorButton::default().allow_none(false), true);
|
||||
let clip = bool_widget(document_node, node_id, 5, "Clip", true);
|
||||
let clip = LayoutGroup::Row { widgets: clip };
|
||||
vec![location, dimensions, background, clip]
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ impl FrontendGraphDataType {
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
|
||||
TaggedValue::GraphicGroup(_) => Self::GraphicGroup,
|
||||
TaggedValue::Artboard(_) => Self::Artboard,
|
||||
TaggedValue::Artboard(_) | TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||
TaggedValue::Palette(_) => Self::Palette,
|
||||
_ => Self::General,
|
||||
}
|
||||
@@ -66,6 +66,8 @@ pub struct FrontendGraphOutput {
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub connected: Option<NodeId>,
|
||||
#[serde(rename = "connectedIndex")]
|
||||
pub connected_index: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -73,6 +75,8 @@ pub struct FrontendNode {
|
||||
pub id: graph_craft::document::NodeId,
|
||||
#[serde(rename = "isLayer")]
|
||||
pub is_layer: bool,
|
||||
#[serde(rename = "canBeLayer")]
|
||||
pub can_be_layer: bool,
|
||||
pub alias: String,
|
||||
pub name: String,
|
||||
#[serde(rename = "primaryInput")]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::nodes::SelectedNodes;
|
||||
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
|
||||
use graph_craft::document::FlowType;
|
||||
use graph_craft::document::{NodeId, NodeNetwork};
|
||||
use graphene_core::renderer::ClickTarget;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::transform::Footprint;
|
||||
@@ -92,14 +93,14 @@ impl DocumentMetadata {
|
||||
sorted_layers
|
||||
}
|
||||
|
||||
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root.
|
||||
/// Ancestor that is shared by all layers and that is deepest (more nested). Default may be the root. Skips selected non-folder, non-artboard layers
|
||||
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>, include_self: bool) -> Option<LayerNodeIdentifier> {
|
||||
layers
|
||||
.map(|layer| {
|
||||
let mut layer_path = layer.ancestors(self).collect::<Vec<_>>();
|
||||
layer_path.reverse();
|
||||
|
||||
if include_self || !self.folders.contains(&layer) {
|
||||
if !include_self || !self.is_artboard(layer) && !self.is_folder(layer) {
|
||||
layer_path.pop();
|
||||
}
|
||||
|
||||
@@ -147,43 +148,52 @@ impl DocumentMetadata {
|
||||
impl DocumentMetadata {
|
||||
/// Loads the structure of layer nodes from a node graph.
|
||||
pub fn load_structure(&mut self, graph: &NodeNetwork, selected_nodes: &mut SelectedNodes) {
|
||||
fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a DocumentNode, NodeId)> {
|
||||
graph.upstream_flow_back_from_nodes(vec![node.inputs[0].as_node()?], true).find(|(node, _)| node.is_layer())
|
||||
}
|
||||
|
||||
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
|
||||
self.artboards = HashSet::new();
|
||||
self.folders = HashSet::new();
|
||||
self.hidden = HashSet::new();
|
||||
self.locked = HashSet::new();
|
||||
|
||||
let id = graph.exports[0].node_id;
|
||||
let Some(output_node) = graph.nodes.get(&id) else {
|
||||
return;
|
||||
};
|
||||
let Some((layer_node, node_id)) = first_child_layer(graph, output_node) else {
|
||||
return;
|
||||
};
|
||||
let parent = LayerNodeIdentifier::ROOT;
|
||||
let mut stack = vec![(layer_node, node_id, parent)];
|
||||
while let Some((node, node_id, parent)) = stack.pop() {
|
||||
let mut current = Some((node, node_id));
|
||||
while let Some(&(current_node, current_node_id)) = current.as_ref() {
|
||||
let current_layer_id = LayerNodeIdentifier::new_unchecked(current_node_id);
|
||||
if !self.structure.contains_key(¤t_layer_id) {
|
||||
parent.push_child(self, current_layer_id);
|
||||
// Refers to output node: NodeId(0)
|
||||
let output_node_id = graph.exports[0].node_id;
|
||||
|
||||
if let Some((child_node, child_id)) = first_child_layer(graph, current_node) {
|
||||
stack.push((child_node, child_id, current_layer_id));
|
||||
}
|
||||
let mut awaiting_horizontal_flow = vec![(output_node_id, LayerNodeIdentifier::ROOT)];
|
||||
let mut awaiting_primary_flow = vec![];
|
||||
|
||||
if is_artboard(current_layer_id, graph) {
|
||||
self.artboards.insert(current_layer_id);
|
||||
}
|
||||
if is_folder(current_layer_id, graph) {
|
||||
self.folders.insert(current_layer_id);
|
||||
}
|
||||
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {
|
||||
let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow);
|
||||
// Skip the horizontal_root_node_id node
|
||||
for (current_node, current_node_id) in horizontal_flow_iter.skip(1) {
|
||||
if !current_node.visible {
|
||||
self.hidden.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.locked {
|
||||
self.locked.insert(current_node_id);
|
||||
}
|
||||
|
||||
if current_node.is_layer {
|
||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
|
||||
if !self.structure.contains_key(¤t_layer_node) {
|
||||
awaiting_primary_flow.push((current_node_id, parent_layer_node));
|
||||
parent_layer_node.push_child(self, current_layer_node);
|
||||
parent_layer_node = current_layer_node;
|
||||
|
||||
if is_artboard(current_layer_node, graph) {
|
||||
self.artboards.insert(current_layer_node);
|
||||
}
|
||||
|
||||
if graph.nodes.get(¤t_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
|
||||
self.folders.insert(current_layer_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((primary_root_node_id, parent_layer_node)) = awaiting_primary_flow.pop() {
|
||||
let primary_flow_iter = graph.upstream_flow_back_from_nodes(vec![primary_root_node_id], FlowType::PrimaryFlow);
|
||||
// Skip the primary_root_node_id node
|
||||
for (current_node, current_node_id) in primary_flow_iter.skip(1) {
|
||||
if !current_node.visible {
|
||||
self.hidden.insert(current_node_id);
|
||||
}
|
||||
@@ -191,11 +201,26 @@ impl DocumentMetadata {
|
||||
if current_node.locked {
|
||||
self.locked.insert(current_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the sibling below
|
||||
let construct_layer_node = ¤t_node.inputs[1];
|
||||
current = construct_layer_node.as_node().and_then(|id| graph.nodes.get(&id).filter(|node| node.is_layer()).map(|node| (node, id)));
|
||||
if current_node.is_layer {
|
||||
// Create a new layer for the top of each stack, and add it as a child to the previous parent
|
||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
|
||||
if !self.structure.contains_key(¤t_layer_node) {
|
||||
parent_layer_node.push_child(self, current_layer_node);
|
||||
|
||||
// The layer nodes for the horizontal flow is itself
|
||||
awaiting_horizontal_flow.push((current_node_id, current_layer_node));
|
||||
|
||||
if is_artboard(current_layer_node, graph) {
|
||||
self.artboards.insert(current_layer_node);
|
||||
}
|
||||
|
||||
if graph.nodes.get(¤t_layer_node.to_node()).map(|node| node.layer_has_child_layers(graph)).unwrap_or_default() {
|
||||
self.folders.insert(current_layer_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +248,7 @@ impl DocumentMetadata {
|
||||
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
|
||||
layer
|
||||
.ancestors(self)
|
||||
.filter_map(|layer| self.upstream_transforms.get(&layer.to_node()))
|
||||
.filter_map(|ancestor_layer| self.upstream_transforms.get(&ancestor_layer.to_node()))
|
||||
.copied()
|
||||
.map(|(footprint, transform)| footprint.transform * transform)
|
||||
.next()
|
||||
@@ -351,7 +376,7 @@ impl LayerNodeIdentifier {
|
||||
#[track_caller]
|
||||
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
|
||||
debug_assert!(
|
||||
node_id == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node_id).is_some_and(|node| node.is_layer()),
|
||||
node_id == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node_id).is_some_and(|node| node.is_layer),
|
||||
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
|
||||
network.nodes.get(&node_id)
|
||||
);
|
||||
@@ -622,15 +647,8 @@ struct NodeRelations {
|
||||
// ================
|
||||
|
||||
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
network.upstream_flow_back_from_nodes(vec![layer.to_node()], true).any(|(node, _)| node.is_artboard())
|
||||
}
|
||||
|
||||
pub fn is_folder(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
|
||||
network.nodes.get(&layer.to_node()).and_then(|node| node.inputs.first()).is_some_and(|input| input.as_node().is_none())
|
||||
|| network
|
||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], true)
|
||||
.skip(1)
|
||||
.any(|(node, _)| node.is_artboard() || node.is_layer())
|
||||
let Some(node) = network.nodes.get(&layer.to_node()) else { return false };
|
||||
node.is_artboard()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -30,24 +30,18 @@ impl serde::Serialize for JsRawBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
pub enum LayerClassification {
|
||||
#[default]
|
||||
Folder,
|
||||
Artboard,
|
||||
Layer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
pub struct LayerPanelEntry {
|
||||
pub id: NodeId,
|
||||
pub name: String,
|
||||
pub alias: String,
|
||||
pub tooltip: String,
|
||||
#[serde(rename = "layerClassification")]
|
||||
pub layer_classification: LayerClassification,
|
||||
#[serde(rename = "childrenAllowed")]
|
||||
pub children_allowed: bool,
|
||||
#[serde(rename = "childrenPresent")]
|
||||
pub children_present: bool,
|
||||
pub expanded: bool,
|
||||
#[serde(rename = "hasChildren")]
|
||||
pub has_children: bool,
|
||||
pub depth: usize,
|
||||
pub visible: bool,
|
||||
#[serde(rename = "parentsVisible")]
|
||||
pub parents_visible: bool,
|
||||
@@ -56,7 +50,6 @@ pub struct LayerPanelEntry {
|
||||
pub parents_unlocked: bool,
|
||||
#[serde(rename = "parentId")]
|
||||
pub parent_id: Option<NodeId>,
|
||||
pub depth: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
||||
|
||||
@@ -186,7 +186,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
let node = layer.to_node();
|
||||
let previous_alias = active_document.network().nodes.get(&node).map(|node| node.alias.clone()).unwrap_or_default();
|
||||
|
||||
let Some(node) = active_document.network().nodes.get(&node).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else {
|
||||
let Some(node) = active_document
|
||||
.network()
|
||||
.nodes
|
||||
.get(&node)
|
||||
.and_then(|node| if node.is_layer { node.inputs.get(1) } else { node.inputs.get(0) })
|
||||
.and_then(|input| input.as_node())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -195,7 +201,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
active_document.network(),
|
||||
&active_document
|
||||
.network()
|
||||
.upstream_flow_back_from_nodes(vec![node], false)
|
||||
.upstream_flow_back_from_nodes(vec![node], graph_craft::document::FlowType::UpstreamFlow)
|
||||
.enumerate()
|
||||
.map(|(index, (_, node_id))| (node_id, NodeId(index as u64)))
|
||||
.collect(),
|
||||
@@ -656,9 +662,7 @@ impl PortfolioMessageHandler {
|
||||
return;
|
||||
};
|
||||
|
||||
self.executor.poll_node_graph_evaluation(active_document, responses).unwrap_or_else(|e| {
|
||||
log::error!("Error while evaluating node graph: {e}");
|
||||
|
||||
if self.executor.poll_node_graph_evaluation(active_document, responses).is_err() {
|
||||
let error = r#"
|
||||
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
||||
@@ -669,6 +673,6 @@ impl PortfolioMessageHandler {
|
||||
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
||||
.to_string();
|
||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,11 +157,11 @@ pub fn get_opacity(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -
|
||||
}
|
||||
|
||||
pub fn get_fill_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document_network).node_id("Fill")
|
||||
NodeGraphLayer::new(layer, document_network).upstream_node_id_from_name("Fill")
|
||||
}
|
||||
|
||||
pub fn get_text_id(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<NodeId> {
|
||||
NodeGraphLayer::new(layer, document_network).node_id("Text")
|
||||
NodeGraphLayer::new(layer, document_network).upstream_node_id_from_name("Text")
|
||||
}
|
||||
|
||||
/// Gets properties from the Text node
|
||||
@@ -233,21 +233,21 @@ impl<'a> NodeGraphLayer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an iterator up the primary flow of the layer
|
||||
pub fn primary_layer_flow(&self) -> impl Iterator<Item = (&'a DocumentNode, NodeId)> {
|
||||
self.node_graph.upstream_flow_back_from_nodes(vec![self.layer_node], true)
|
||||
/// Return an iterator up the horizontal flow of the layer
|
||||
pub fn horizontal_layer_flow(&self) -> impl Iterator<Item = (&'a DocumentNode, NodeId)> {
|
||||
self.node_graph.upstream_flow_back_from_nodes(vec![self.layer_node], graph_craft::document::FlowType::HorizontalFlow)
|
||||
}
|
||||
|
||||
/// Node id of a node if it exists in the layer's primary flow
|
||||
pub fn node_id(&self, node_name: &str) -> Option<NodeId> {
|
||||
self.primary_layer_flow().find(|(node, _id)| node.name == node_name).map(|(_node, id)| id)
|
||||
pub fn upstream_node_id_from_name(&self, node_name: &str) -> Option<NodeId> {
|
||||
self.horizontal_layer_flow().find(|(node, _)| node.name == node_name).map(|(_, id)| id)
|
||||
}
|
||||
|
||||
/// Find all of the inputs of a specific node within the layer's primary flow, up until the next layer is reached.
|
||||
pub fn find_node_inputs(&self, node_name: &str) -> Option<&'a Vec<NodeInput>> {
|
||||
self.primary_layer_flow()
|
||||
.skip(1)
|
||||
.take_while(|(node, _)| !node.is_layer())
|
||||
self.horizontal_layer_flow()
|
||||
.skip(1)// Skip self
|
||||
.take_while(|(node, _)| !node.is_layer)
|
||||
.find(|(node, _)| node.name == node_name)
|
||||
.map(|(node, _id)| &node.inputs)
|
||||
}
|
||||
|
||||
@@ -432,16 +432,16 @@ pub fn are_manipulator_handles_colinear<Id: bezier_rs::Identifier>(group: &bezie
|
||||
|
||||
pub fn get_layer_snap_points(layer: LayerNodeIdentifier, snap_data: &SnapData, points: &mut Vec<SnapCandidatePoint>) {
|
||||
let document = snap_data.document;
|
||||
|
||||
if document.metadata().is_artboard(layer) {
|
||||
} else if document.metadata().is_folder(layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if document.metadata().is_folder(layer) {
|
||||
for child in layer.descendants(document.metadata()) {
|
||||
get_layer_snap_points(child, snap_data, points);
|
||||
}
|
||||
} else {
|
||||
// Skip empty paths
|
||||
if document.metadata.layer_outline(layer).next().is_none() {
|
||||
return;
|
||||
}
|
||||
} else if document.metadata.layer_outline(layer).next().is_some() {
|
||||
let to_document = document.metadata.transform_to_document(layer);
|
||||
for subpath in document.metadata.layer_outline(layer) {
|
||||
subpath_anchor_snap_points(layer, subpath, snap_data, points, to_document);
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::application::generate_uuid;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
|
||||
@@ -24,6 +23,7 @@ pub enum ArtboardToolMessage {
|
||||
Overlays(OverlayContext),
|
||||
|
||||
// Tool-specific messages
|
||||
UpdateSelectedArtboard,
|
||||
DeleteSelected,
|
||||
NudgeSelected { delta_x: f64, delta_y: f64 },
|
||||
PointerDown,
|
||||
@@ -125,7 +125,7 @@ impl ArtboardToolData {
|
||||
|
||||
let mut intersections = document
|
||||
.click_xray(input.mouse.position)
|
||||
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network, "Artboard"));
|
||||
.filter(|&layer| document.network.nodes.get(&layer.to_node()).map_or(false, |document_node| document_node.is_artboard()));
|
||||
|
||||
if let Some(intersection) = intersections.next() {
|
||||
self.selected_artboard = Some(intersection);
|
||||
@@ -136,6 +136,8 @@ impl ArtboardToolData {
|
||||
bounding_box_manager.transform = document.metadata().document_to_viewport;
|
||||
}
|
||||
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![intersection.to_node()] });
|
||||
|
||||
true
|
||||
} else {
|
||||
self.selected_artboard = None;
|
||||
@@ -278,10 +280,8 @@ impl Fsm for ArtboardToolFsmState {
|
||||
});
|
||||
} else {
|
||||
let id = NodeId(generate_uuid());
|
||||
tool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id));
|
||||
|
||||
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
|
||||
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
tool_data.selected_artboard = Some(LayerNodeIdentifier::new_unchecked(id));
|
||||
|
||||
responses.add(GraphOperationMessage::NewArtboard {
|
||||
id,
|
||||
@@ -377,11 +377,13 @@ impl Fsm for ArtboardToolFsmState {
|
||||
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::UpdateSelectedArtboard) => {
|
||||
tool_data.selected_artboard = document.selected_nodes.selected_layers(document.metadata()).find(|layer| document.metadata().is_artboard(*layer));
|
||||
self
|
||||
}
|
||||
(_, ArtboardToolMessage::DeleteSelected) => {
|
||||
if let Some(artboard) = tool_data.selected_artboard.take() {
|
||||
let id = artboard.to_node();
|
||||
responses.add(GraphOperationMessage::DeleteArtboard { id });
|
||||
}
|
||||
tool_data.selected_artboard.take();
|
||||
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
|
||||
ArtboardToolFsmState::Ready
|
||||
}
|
||||
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
||||
|
||||
@@ -267,8 +267,8 @@ impl BrushToolData {
|
||||
};
|
||||
|
||||
self.layer = Some(layer);
|
||||
for (node, node_id) in document.network().upstream_flow_back_from_nodes(vec![layer.to_node()], true) {
|
||||
if node.name == "Brush" {
|
||||
for (node, node_id) in document.network().upstream_flow_back_from_nodes(vec![layer.to_node()], graph_craft::document::FlowType::HorizontalFlow) {
|
||||
if node.name == "Brush" && node_id != layer.to_node() {
|
||||
let points_input = node.inputs.get(2)?;
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::BrushStrokes(strokes),
|
||||
|
||||
@@ -325,7 +325,7 @@ impl SelectToolData {
|
||||
document.network(),
|
||||
&document
|
||||
.network()
|
||||
.upstream_flow_back_from_nodes(vec![node], false)
|
||||
.upstream_flow_back_from_nodes(vec![node], graph_craft::document::FlowType::UpstreamFlow)
|
||||
.enumerate()
|
||||
.map(|(index, (_, node_id))| (node_id, NodeId(index as u64)))
|
||||
.collect(),
|
||||
@@ -365,8 +365,9 @@ impl SelectToolData {
|
||||
|
||||
// Delete the duplicated layers
|
||||
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) {
|
||||
responses.add(GraphOperationMessage::DeleteLayer {
|
||||
id: layer_ancestors.last().unwrap().to_node(),
|
||||
responses.add(NodeGraphMessage::DeleteNodes {
|
||||
node_ids: vec![layer_ancestors.last().unwrap().to_node()],
|
||||
reconnect: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -275,14 +275,28 @@ impl NodeRuntime {
|
||||
continue;
|
||||
};
|
||||
|
||||
enum IntrospectedData<'a> {
|
||||
GraphicElement(&'a graphene_core::GraphicElement),
|
||||
Artboard(&'a graphene_core::Artboard),
|
||||
}
|
||||
|
||||
let introspected_data_output = introspected_data
|
||||
.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>()
|
||||
.and_then(|io_data| Some(IntrospectedData::GraphicElement(&io_data.output)))
|
||||
.or_else(|| {
|
||||
introspected_data
|
||||
.downcast_ref::<IORecord<Footprint, graphene_core::Artboard>>()
|
||||
.and_then(|io_data| Some(IntrospectedData::Artboard(&io_data.output)))
|
||||
});
|
||||
|
||||
let graphic_element = match introspected_data_output {
|
||||
Some(IntrospectedData::GraphicElement(graphic_element)) => Some(graphic_element.clone()),
|
||||
Some(IntrospectedData::Artboard(artboard)) => Some(artboard.clone().into()),
|
||||
_ => None,
|
||||
};
|
||||
// If this is `GraphicElement` data:
|
||||
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
|
||||
if let Some(io_data) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() {
|
||||
let graphic_element = &io_data.output;
|
||||
|
||||
// UPDATE CLICK TARGETS
|
||||
|
||||
// Get the previously stored click targets and wipe them out, then regenerate them
|
||||
if let Some(graphic_element) = graphic_element {
|
||||
let click_targets = self.click_targets.entry(parent_network_node_id).or_default();
|
||||
click_targets.clear();
|
||||
graphic_element.add_click_targets(click_targets);
|
||||
|
||||
Reference in New Issue
Block a user