Allow groups to work with the node graph (#1452)

* Initial groups

* Improve graph arangement

* Fix selecting nested layers

* Code review pass

* Change log

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-11-14 17:17:14 +00:00
committed by GitHub
parent f4ec76f35e
commit 58660f5548
19 changed files with 397 additions and 306 deletions

View File

@@ -77,6 +77,7 @@ impl DocumentMetadata {
self.structure.entry(node_identifier).or_default()
}
/// Layers excluding ones that are children of other layers in the list.
pub fn shallowest_unique_layers(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<Vec<LayerNodeIdentifier>> {
let mut sorted_layers = layers
.map(|layer| {
@@ -90,6 +91,34 @@ impl DocumentMetadata {
sorted_layers.dedup_by(|a, b| a.starts_with(b));
sorted_layers
}
/// Ancestor that is shared by all layers and that is deepest (more nested). May be the root layer.
pub fn deepest_common_ancestor(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> LayerNodeIdentifier {
layers
.map(|layer| {
let mut layer_path = layer.ancestors(self).skip(1).collect::<Vec<_>>();
layer_path.reverse();
layer_path
})
.reduce(|mut a, b| {
a.truncate(a.iter().zip(b.iter()).position(|(&a, &b)| a != b).unwrap_or_else(|| a.len().min(b.len())));
a
})
.and_then(|path| path.last().copied())
.unwrap_or(LayerNodeIdentifier::ROOT)
}
/// Filter out non folder layers
pub fn folders<'a>(&'a self, layers: impl Iterator<Item = LayerNodeIdentifier> + 'a) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
layers.filter(|layer| layer.has_children(self))
}
/// Folders sorted from most nested to least nested
pub fn folders_sorted_by_most_nested(&self, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Vec<LayerNodeIdentifier> {
let mut folders: Vec<_> = self.folders(layers).collect();
folders.sort_by_cached_key(|a| std::cmp::Reverse(a.ancestors(self).count()));
folders
}
}
// selected layer modifications
@@ -146,7 +175,7 @@ impl DocumentMetadata {
}
fn first_child_layer<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a DocumentNode, NodeId)> {
graph.primary_flow_from_opt(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer")
graph.primary_flow_from_node(Some(node.inputs[0].as_node()?)).find(|(node, _)| node.name == "Layer")
}
fn sibling_below<'a>(graph: &'a NodeNetwork, node: &DocumentNode) -> Option<(&'a DocumentNode, NodeId)> {
node.inputs[7].as_node().and_then(|id| graph.nodes.get(&id).filter(|node| node.name == "Layer").map(|node| (node, id)))
@@ -178,7 +207,7 @@ impl DocumentMetadata {
}
fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
network.primary_flow_from_opt(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
network.primary_flow_from_node(Some(layer.to_node())).any(|(node, _)| node.name == "Artboard")
}
// click targets
@@ -299,6 +328,7 @@ impl LayerNodeIdentifier {
}
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node
#[track_caller]
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
debug_assert!(
is_layer_node(node_id, network),

View File

@@ -22,11 +22,6 @@ pub enum DocumentResponse {
LayerChanged {
path: Vec<LayerId>,
},
MoveSelectedLayersTo {
folder_path: Vec<LayerId>,
insert_index: isize,
reverse_index: bool,
},
DeletedSelectedManipulatorPoints,
}
@@ -39,7 +34,6 @@ impl fmt::Display for DocumentResponse {
DocumentResponse::LayerChanged { .. } => write!(f, "LayerChanged"),
DocumentResponse::DeletedLayer { .. } => write!(f, "DeleteLayer"),
DocumentResponse::DeletedSelectedManipulatorPoints { .. } => write!(f, "DeletedSelectedManipulatorPoints"),
DocumentResponse::MoveSelectedLayersTo { .. } => write!(f, "MoveSelectedLayersTo"),
}
}
}

View File

@@ -302,7 +302,7 @@ mod test {
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().document_legacy.clone();
@@ -341,7 +341,7 @@ mod test {
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});
@@ -407,12 +407,12 @@ mod test {
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});
@@ -479,12 +479,12 @@ mod test {
editor.draw_rect(0., 800., 12., 200.);
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: vec![],
parent: LayerNodeIdentifier::ROOT,
insert_index: -1,
});

View File

@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::prelude::*;
use document_legacy::document::Document as DocumentLegacy;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::blend_mode::BlendMode;
use document_legacy::layers::style::ViewMode;
use document_legacy::LayerId;
@@ -106,9 +107,8 @@ pub enum DocumentMessage {
affected_layer_path: Vec<LayerId>,
},
MoveSelectedLayersTo {
folder_path: Vec<LayerId>,
parent: LayerNodeIdentifier,
insert_index: isize,
reverse_index: bool,
},
NudgeSelectedLayers {
delta_x: f64,
@@ -169,6 +169,9 @@ pub enum DocumentMessage {
SetOverlaysVisibility {
visible: bool,
},
SetRangeSelectionLayer {
new_layer: Option<LayerNodeIdentifier>,
},
SetSelectedLayers {
replacement_selected_layers: Vec<Vec<LayerId>>,
},
@@ -189,9 +192,6 @@ pub enum DocumentMessage {
},
Undo,
UndoFinished,
UngroupLayers {
folder_path: Vec<LayerId>,
},
UngroupSelectedLayers,
UpdateDocumentTransform {
transform: glam::DAffine2,

View File

@@ -55,7 +55,7 @@ pub struct DocumentMessageHandler {
#[serde(with = "vectorize_layer_metadata")]
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
layer_range_selection_reference: Vec<LayerId>,
layer_range_selection_reference: Option<LayerNodeIdentifier>,
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
@@ -88,7 +88,7 @@ impl Default for DocumentMessageHandler {
undo_in_progress: false,
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
layer_range_selection_reference: Vec::new(),
layer_range_selection_reference: None,
navigation_handler: NavigationMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
@@ -134,15 +134,6 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.layer_metadata.remove(path);
}
DocumentResponse::LayerChanged { path } => responses.add(LayerChanged { affected_layer_path: path.clone() }),
DocumentResponse::MoveSelectedLayersTo {
folder_path,
insert_index,
reverse_index,
} => responses.add(MoveSelectedLayersTo {
folder_path: folder_path.clone(),
insert_index: *insert_index,
reverse_index: *reverse_index,
}),
DocumentResponse::CreatedLayer { .. } => {
unimplemented!("We should no longer be creating layers in the document and should instead be using the node graph.")
}
@@ -318,7 +309,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
DeselectAllLayers => {
responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] });
self.layer_range_selection_reference.clear();
self.layer_range_selection_reference = None;
}
DirtyRenderDocument => {
// Mark all non-overlay caches as dirty
@@ -350,7 +341,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
DuplicateSelectedLayers => {
self.backup(responses);
responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] });
self.layer_range_selection_reference.clear();
self.layer_range_selection_reference = None;
for path in self.selected_layers_sorted() {
responses.add(DocumentOperation::DuplicateLayer { path: path.to_vec() });
}
@@ -439,30 +430,26 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
GroupSelectedLayers => {
// TODO: Add code that changes the insert index of the new folder based on the selected layer
let mut new_folder_path = self.document_legacy.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
let parent = self.metadata().deepest_common_ancestor(self.metadata().selected_layers());
// Required for grouping parent folders with their own children
if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) {
new_folder_path.remove(new_folder_path.len() - 1);
}
new_folder_path.push(generate_uuid());
let folder_id = generate_uuid();
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
responses.add(DocumentMessage::DeleteSelectedLayers);
responses.add(DocumentOperation::CreateFolder {
path: new_folder_path.clone(),
responses.add(GraphOperationMessage::NewCustomLayer {
id: folder_id,
nodes: HashMap::new(),
parent,
insert_index: -1,
});
responses.add(DocumentMessage::ToggleLayerExpansion { layer_path: new_folder_path.clone() });
responses.add(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: new_folder_path.clone(),
parent: LayerNodeIdentifier::new_unchecked(folder_id),
insert_index: -1,
});
responses.add(DocumentMessage::SetSelectedLayers {
replacement_selected_layers: vec![new_folder_path],
});
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] });
}
ImaginateClear { layer_path } => responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }),
ImaginateGenerate { layer_path } => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path }),
@@ -496,25 +483,21 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path });
self.update_layer_tree_options_bar_widgets(responses, &render_data);
}
MoveSelectedLayersTo {
folder_path,
insert_index,
reverse_index,
} => {
let selected_layers = self.selected_layers().collect::<Vec<_>>();
MoveSelectedLayersTo { parent, insert_index } => {
let selected_layers = self.metadata().selected_layers().collect::<Vec<_>>();
// Prevent trying to insert into self
if selected_layers.iter().any(|layer| folder_path.starts_with(layer)) {
// Disallow trying to insert into self
if selected_layers.iter().any(|&layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer)) {
return;
}
let insert_index = self.update_insert_index(&selected_layers, &folder_path, insert_index, reverse_index).unwrap();
let insert_index = self.update_insert_index(&selected_layers, parent, insert_index).unwrap();
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
responses.add(DocumentMessage::DeleteSelectedLayers);
responses.add(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path,
parent,
insert_index,
});
}
@@ -700,42 +683,47 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.selected_layers_reorder(relative_index_offset, responses);
}
SelectLayer { layer_path, ctrl, shift } => {
let mut paths = vec![];
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
let clicked_node = *layer_path.last().expect("Cannot select root");
let layer = LayerNodeIdentifier::new(clicked_node, self.network());
let mut nodes = vec![];
// If we have shift pressed and a layer already selected then fill the range
if shift && last_selection_exists {
if let Some(last_selected) = self.layer_range_selection_reference.filter(|_| shift) {
nodes.push(last_selected.to_node());
nodes.push(clicked_node);
// Fill the selection range
self.layer_metadata
.iter()
.filter(|(target, _)| self.document_legacy.layer_is_between(target, &layer_path, &self.layer_range_selection_reference))
.for_each(|(layer_path, _)| {
paths.push(layer_path.clone());
});
self.metadata()
.all_layers()
.skip_while(|&node| node != layer && node != last_selected)
.skip(1)
.take_while(|&node| node != layer && node != last_selected)
.for_each(|node| nodes.push(node.to_node()));
} else {
if ctrl {
// Toggle selection when holding ctrl
let layer = self.layer_metadata_mut(&layer_path);
layer.selected = !layer.selected;
responses.add(LayerChanged {
affected_layer_path: layer_path.clone(),
});
if self.metadata().selected_layers_contains(layer) {
responses.add_front(NodeGraphMessage::SelectedNodesRemove { nodes: vec![clicked_node] });
} else {
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![clicked_node] });
}
responses.add(BroadcastEvent::SelectionChanged);
} else {
paths.push(layer_path.clone());
nodes.push(clicked_node);
}
// Set our last selection reference
self.layer_range_selection_reference = layer_path;
self.layer_range_selection_reference = Some(layer);
}
// Don't create messages for empty operations
if !paths.is_empty() {
if !nodes.is_empty() {
// Add or set our selected layers
if ctrl {
responses.add_front(AddSelectedLayers { additional_layers: paths });
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes });
} else {
responses.add_front(SetSelectedLayers { replacement_selected_layers: paths });
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes });
}
}
}
@@ -802,6 +790,9 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(OverlaysMessage::ClearAllOverlays);
responses.add(OverlaysMessage::Rerender);
}
SetRangeSelectionLayer { new_layer } => {
self.layer_range_selection_reference = new_layer;
}
SetSelectedLayers { replacement_selected_layers } => {
let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected);
selected.for_each(|(path, layer_metadata)| {
@@ -854,36 +845,28 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(UndoFinished);
}
UndoFinished => self.undo_in_progress = false,
UngroupLayers { folder_path } => {
// Select all the children of the folder
let select = self.document_legacy.folder_children_paths(&folder_path);
let message_buffer: [Message; 4] = [
// Select them
DocumentMessage::SetSelectedLayers { replacement_selected_layers: select }.into(),
// Copy them
PortfolioMessage::Copy { clipboard: Clipboard::Internal }.into(),
// Paste them into the folder above
PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
folder_path: folder_path[..folder_path.len() - 1].to_vec(),
insert_index: -1,
}
.into(),
// Delete the parent folder
DocumentMessage::DeleteLayer { layer_path: folder_path }.into(),
];
// Push these messages in reverse due to push_front
for message in message_buffer.into_iter().rev() {
responses.add_front(message);
}
}
UngroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction);
let folder_paths = self.document_legacy.sorted_folders_by_depth(self.selected_layers());
for folder_path in folder_paths {
responses.add(DocumentMessage::UngroupLayers { folder_path: folder_path.to_vec() });
let folder_paths = self.metadata().folders_sorted_by_most_nested(self.metadata().selected_layers());
for folder in folder_paths {
// Select all the children of the folder
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: folder.children(self.metadata()).map(LayerNodeIdentifier::to_node).collect(),
});
// Copy them
responses.add(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
// Paste them into the folder above
responses.add(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: folder.parent(self.metadata()).unwrap_or(LayerNodeIdentifier::ROOT),
insert_index: -1,
});
// Delete the parent folder
responses.add(GraphOperationMessage::DeleteLayer { id: folder.to_node() });
}
responses.add(DocumentMessage::CommitTransaction);
}
@@ -1415,12 +1398,11 @@ impl DocumentMessageHandler {
/// 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: &[&[LayerId]], path: &[LayerId], insert_index: isize, reverse_index: bool) -> Result<isize, DocumentError> {
let folder = self.document_legacy.folder(path)?;
let insert_index = if reverse_index { folder.layer_ids.len() as isize - insert_index } else { insert_index };
let layer_ids_above = if insert_index < 0 { &folder.layer_ids } else { &folder.layer_ids[..(insert_index as usize)] };
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> Result<isize, DocumentError> {
let layer_ids_above = parent.children(self.metadata()).take(if insert_index < 0 { usize::MAX } else { insert_index as usize });
let new_insert_index = layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as isize;
Ok(insert_index - layer_ids_above.iter().filter(|layer_id| layers.iter().any(|x| *x == [path, &[**layer_id]].concat())).count() as isize)
Ok(new_insert_index)
}
/// Calculates the bounding box of all layers in the document
@@ -1739,53 +1721,39 @@ impl DocumentMessageHandler {
pub fn selected_layers_reorder(&mut self, relative_index_offset: isize, responses: &mut VecDeque<Message>) {
self.backup(responses);
let all_layer_paths = self.all_layers_sorted();
let selected_layers = self.selected_layers_sorted();
let mut selected_layers = self.metadata().selected_layers();
let first_or_last_selected_layer = match relative_index_offset.signum() {
-1 => selected_layers.first(),
-1 => selected_layers.next(),
1 => selected_layers.last(),
_ => panic!("selected_layers_reorder() must be given a non-zero value"),
};
if let Some(pivot_layer) = first_or_last_selected_layer {
let sibling_layer_paths: Vec<_> = all_layer_paths
.iter()
.filter(|layer| {
// Check if this is a sibling of the pivot layer
// TODO: Break this out into a reusable function `fn are_layers_siblings(layer_a, layer_b) -> bool`
let containing_folder_path = &pivot_layer[0..pivot_layer.len() - 1];
layer.starts_with(containing_folder_path) && pivot_layer.len() == layer.len()
})
.collect();
let Some(pivot_layer) = first_or_last_selected_layer else {
return;
};
let Some(parent) = pivot_layer.parent(self.metadata()) else {
return;
};
// TODO: Break this out into a reusable function: `fn layer_index_in_containing_folder(layer_path) -> usize`
let pivot_index_among_siblings = sibling_layer_paths.iter().position(|path| *path == pivot_layer);
let sibling_layer_paths: Vec<_> = parent.children(self.metadata()).collect();
let Some(pivot_index) = sibling_layer_paths.iter().position(|path| *path == pivot_layer) else {
return;
};
if let Some(pivot_index) = pivot_index_among_siblings {
let max = sibling_layer_paths.len() as i64 - 1;
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
let max = sibling_layer_paths.len() as i64 - 1;
let insert_index = (pivot_index as i64 + relative_index_offset as i64).clamp(0, max) as usize;
let existing_layer_to_insert_beside = sibling_layer_paths.get(insert_index);
let Some(&neighbor) = sibling_layer_paths.get(insert_index) else {
return;
};
let Some(neighbor_index) = sibling_layer_paths.iter().position(|path| *path == neighbor) else {
return;
};
// TODO: Break this block out into a call to a message called `MoveSelectedLayersNextToLayer { neighbor_path, above_or_below }`
if let Some(neighbor_path) = existing_layer_to_insert_beside {
let (neighbor_id, folder_path) = neighbor_path.split_last().expect("Can't move the root folder");
// 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;
if let Some(folder) = self.document_legacy.layer(folder_path).ok().and_then(|layer| layer.as_folder().ok()) {
let neighbor_layer_index = folder.layer_ids.iter().position(|id| id == neighbor_id).unwrap() as isize;
// If moving down, insert below this layer. If moving up, insert above this layer.
let insert_index = if relative_index_offset < 0 { neighbor_layer_index } else { neighbor_layer_index + 1 };
responses.add(DocumentMessage::MoveSelectedLayersTo {
folder_path: folder_path.to_vec(),
insert_index,
reverse_index: false,
});
}
}
}
}
responses.add(DocumentMessage::MoveSelectedLayersTo { parent, insert_index });
}
}

View File

@@ -1,6 +1,8 @@
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use document_legacy::document_metadata::LayerNodeIdentifier;
use graph_craft::document::DocumentNode;
use graph_craft::document::NodeId;
use graphene_core::raster::ImageFrame;
use graphene_core::text::Font;
@@ -65,6 +67,12 @@ pub enum GraphOperationMessage {
id: NodeId,
image_frame: ImageFrame<Color>,
},
NewCustomLayer {
id: NodeId,
nodes: HashMap<NodeId, DocumentNode>,
parent: LayerNodeIdentifier,
insert_index: isize,
},
NewVectorLayer {
id: NodeId,
subpaths: Vec<Subpath<ManipulatorGroupId>>,

View File

@@ -92,30 +92,47 @@ impl<'a> ModifyInputsContext<'a> {
Some(new_id)
}
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize) -> Option<NodeId> {
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, input_index: usize, skip_layer_nodes: usize) -> Option<NodeId> {
assert!(!self.network.nodes.contains_key(&new_id), "Creating already existing layer");
let output = NodeOutput::new(output_node_id, input_index);
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
let new_id = if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] {
if let NodeInput::Node { node_id, output_index, .. } = &self.network.nodes.get(&output_node_id)?.inputs[input_index] {
let sibling_node = &self.network.nodes.get(node_id)?;
let node_id = *node_id;
let output_index = *output_index;
let sibling_layer = if sibling_node.name == "Layer" {
if sibling_node.name == "Layer" {
// There is already a layer node
NodeOutput::new(node_id, 0)
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(generate_uuid(), NodeOutput::new(node_id, output_index), output, node, 0, 0, IVec2::new(-8, 0))?;
NodeOutput::new(node_id, 0)
};
sibling_layer = Some(NodeOutput::new(node_id, 0));
}
let node = resolve_document_node_type("Layer").expect("Layer node").default_document_node();
self.insert_between(new_id, sibling_layer, output, node, 7, 0, IVec2::new(0, 3))
// Skip some layer nodes
for _ in 0..skip_layer_nodes {
if let Some(old_sibling) = &sibling_layer {
output = NodeOutput::new(old_sibling.node_id, 7);
sibling_layer = self.network.nodes.get(&old_sibling.node_id)?.inputs[7].as_node().map(|node| NodeOutput::new(node, 0));
shift = IVec2::new(0, 3);
}
}
// Insert at top of stack
} else {
let layer_node = resolve_document_node_type("Layer").expect("Node").default_document_node();
self.insert_node_before(new_id, output_node_id, input_index, layer_node, IVec2::new(-5, 3))
shift = IVec2::new(-8, 3);
}
// 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, 7, 0, shift)
} else {
self.insert_node_before(new_id, output.node_id, output.node_output_index, layer_node, shift)
};
// Update the document metadata structure
@@ -262,7 +279,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Changes the inputs of a specific node
fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let existing_node_id = self.network.primary_flow_from_opt(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id);
let existing_node_id = self.network.primary_flow_from_node(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id);
if let Some(node_id) = existing_node_id {
self.modify_existing_node_inputs(node_id, update_input);
} else {
@@ -285,7 +302,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Changes the inputs of a all of the existing instances of a node name
fn modify_all_node_inputs(&mut self, name: &'static str, skip_rerender: bool, mut update_input: impl FnMut(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let existing_nodes: Vec<_> = self.network.primary_flow_from_opt(self.layer_node).filter(|(node, _)| node.name == name).map(|(_, id)| id).collect();
let existing_nodes: Vec<_> = self.network.primary_flow_from_node(self.layer_node).filter(|(node, _)| node.name == name).map(|(_, id)| id).collect();
for existing_node_id in existing_nodes {
self.modify_existing_node_inputs(existing_node_id, &mut update_input);
}
@@ -460,6 +477,7 @@ impl<'a> ModifyInputsContext<'a> {
LayerNodeIdentifier::new(id, self.network).delete(self.document_metadata);
let new_input = node.inputs[7].clone();
let deleted_position = node.metadata.position;
for post_node in self.outwards_links.get(&id).unwrap_or(&Vec::new()) {
let Some(node) = self.network.nodes.get_mut(post_node) else {
@@ -476,7 +494,7 @@ impl<'a> ModifyInputsContext<'a> {
}
let mut delete_nodes = vec![id];
for (_node, id) in self.network.primary_flow_from_opt(Some(id)) {
for (_node, id) in self.network.primary_flow_from_node(Some(id)) {
if self.outwards_links.get(&id).is_some_and(|outwards| outwards.len() == 1) {
delete_nodes.push(id);
}
@@ -485,6 +503,16 @@ impl<'a> ModifyInputsContext<'a> {
for node_id in &delete_nodes {
self.network.nodes.remove(node_id);
}
if let Some(node_id) = new_input.as_node() {
if let Some(shift) = self.network.nodes.get(&node_id).map(|node| deleted_position - node.metadata.position) {
for node_id in self.network.all_dependencies(node_id).map(|(_, id)| id).collect::<Vec<_>>() {
let Some(node) = self.network.nodes.get_mut(&node_id) else { continue };
node.metadata.position += shift;
}
}
}
self.responses.add(self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id)));
self.responses.add(DocumentMessage::DocumentStructureChanged);
@@ -567,25 +595,75 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
}
GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_artboard(artboard, layer);
}
}
GraphOperationMessage::NewBitmapLayer { id, image_frame } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_image_data(image_frame, layer);
}
}
GraphOperationMessage::NewCustomLayer { id, nodes, parent, insert_index } => {
trace!("Inserting new layer {id} as a child of {parent:?} at index {insert_index}");
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
let skip_layer_nodes = if insert_index < 0 { (-1 - insert_index) as usize } else { insert_index as usize };
let output_node_id = if parent == LayerNodeIdentifier::ROOT {
modify_inputs.network.original_outputs()[0].node_id
} else {
parent.to_node()
};
if let Some(layer) = modify_inputs.create_layer(id, output_node_id, 0, skip_layer_nodes) {
let new_ids: HashMap<_, _> = nodes.iter().map(|(&id, _)| (id, crate::application::generate_uuid())).collect();
let shift = nodes
.get(&0)
.and_then(|node| {
modify_inputs
.network
.nodes
.get(&layer)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
// Shift copied node
document_node.metadata.position += shift;
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
// Insert node into network
modify_inputs.network.nodes.insert(node_id, document_node);
}
if let Some(layer_node) = modify_inputs.network.nodes.get_mut(&layer) {
if let Some(&input) = new_ids.get(&0) {
layer_node.inputs[0] = NodeInput::node(input, 0)
}
}
modify_inputs.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
document.metadata.load_structure(&document.document_network);
}
GraphOperationMessage::NewVectorLayer { id, subpaths } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_vector_data(subpaths, layer);
}
}
GraphOperationMessage::NewTextLayer { id, text, font, size } => {
let mut modify_inputs = ModifyInputsContext::new(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0) {
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.original_outputs()[0].node_id, 0, 0) {
modify_inputs.insert_text(text, font, size, layer);
}
}

View File

@@ -9,9 +9,6 @@ use graph_craft::document::{DocumentNode, NodeId, NodeInput};
pub enum NodeGraphMessage {
// Messages
Init,
AddSelectNodes {
nodes: Vec<NodeId>,
},
SelectedNodesUpdated,
CloseNodeGraph,
ConnectNodesByLink {
@@ -67,6 +64,15 @@ pub enum NodeGraphMessage {
serialized_nodes: String,
},
RunDocumentGraph,
SelectedNodesAdd {
nodes: Vec<NodeId>,
},
SelectedNodesRemove {
nodes: Vec<NodeId>,
},
SelectedNodesSet {
nodes: Vec<NodeId>,
},
SendGraph {
should_rerender: bool,
},
@@ -86,9 +92,6 @@ pub enum NodeGraphMessage {
input_index: usize,
value: TaggedValue,
},
SetSelectedNodes {
nodes: Vec<NodeId>,
},
ShiftNode {
node_id: NodeId,
},

View File

@@ -417,14 +417,14 @@ impl NodeGraphMessageHandler {
}
/// Gets the default node input based on the node name and the input index
fn default_node_input(name: String, index: usize) -> Option<NodeInput> {
pub fn default_node_input(name: String, index: usize) -> Option<NodeInput> {
resolve_document_node_type(&name)
.and_then(|node| node.inputs.get(index))
.map(|input: &DocumentInputType| input.default.clone())
}
/// Returns an iterator of nodes to be copied and their ids, excluding output and input nodes
fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
pub fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
new_ids
.iter()
.filter(|&(&id, _)| !network.outputs_contain(id))
@@ -455,12 +455,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
document.metadata.load_structure(&document.document_network);
responses.add(DocumentMessage::DocumentStructureChanged);
}
NodeGraphMessage::AddSelectNodes { nodes } => {
responses.add(document.metadata.add_selected_nodes(nodes));
}
NodeGraphMessage::SelectedNodesUpdated => {
self.update_selection_action_buttons(document, responses);
self.update_selected(document, responses);
if document.metadata.selected_layers().count() <= 1 {
responses.add(DocumentMessage::SetRangeSelectionLayer {
new_layer: document.metadata.selected_layers().next(),
});
}
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::CloseNodeGraph => {}
NodeGraphMessage::ConnectNodesByLink {
@@ -739,11 +742,21 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
let nodes = new_ids.values().copied().collect();
responses.add(NodeGraphMessage::SetSelectedNodes { nodes });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { document_id, layer_path: Vec::new() }),
NodeGraphMessage::SelectedNodesAdd { nodes } => {
responses.add(document.metadata.add_selected_nodes(nodes));
}
NodeGraphMessage::SelectedNodesRemove { nodes } => {
responses.add(document.metadata.retain_selected_nodes(|node| !nodes.contains(node)));
}
NodeGraphMessage::SelectedNodesSet { nodes } => {
responses.add(document.metadata.set_selected_nodes(nodes));
responses.add(PropertiesPanelMessage::ResendActiveProperties);
}
NodeGraphMessage::SendGraph { should_rerender } => {
if let Some(network) = document.document_network.nested_network(&self.network) {
Self::send_graph(network, &self.layer_path, responses);
@@ -815,10 +828,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
}
}
NodeGraphMessage::SetSelectedNodes { nodes } => {
responses.add(document.metadata.set_selected_nodes(nodes));
responses.add(PropertiesPanelMessage::ResendActiveProperties);
}
NodeGraphMessage::ShiftNode { node_id } => {
let Some(network) = document.document_network.nested_network_mut(&self.network) else {
warn!("No network");

View File

@@ -1,8 +1,8 @@
use super::layer_panel::LayerMetadata;
use document_legacy::layers::layer_info::Layer;
use graph_craft::document::DocumentNode;
use graph_craft::document::NodeId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[repr(u8)]
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
@@ -18,6 +18,7 @@ pub const INTERNAL_CLIPBOARD_COUNT: u8 = Clipboard::_InternalClipboardCount as u
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CopyBufferEntry {
pub layer: Layer,
pub layer_metadata: LayerMetadata,
pub nodes: HashMap<NodeId, DocumentNode>,
pub selected: bool,
pub collapsed: bool,
}

View File

@@ -1,6 +1,7 @@
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use graph_craft::document::NodeId;
use graphene_core::text::Font;
@@ -87,13 +88,9 @@ pub enum PortfolioMessage {
document_is_saved: bool,
document_serialized_content: String,
},
// TODO: Paste message is unused, delete it?
Paste {
clipboard: Clipboard,
},
PasteIntoFolder {
clipboard: Clipboard,
folder_path: Vec<LayerId>,
parent: LayerNodeIdentifier,
insert_index: isize,
},
PasteSerializedData {

View File

@@ -12,7 +12,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup};
use crate::node_graph_executor::NodeGraphExecutor;
use document_legacy::layers::style::RenderData;
use document_legacy::Operation as DocumentOperation;
use graph_craft::document::NodeId;
use graphene_core::text::Font;
@@ -166,30 +165,49 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
PortfolioMessage::Copy { clipboard } => {
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
if let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) {
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
for layer_path in active_document.selected_layers_without_children() {
match (active_document.document_legacy.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
(Ok(layer), layer_metadata) => {
buffer.push(CopyBufferEntry { layer, layer_metadata });
}
(Err(e), _) => warn!("Could not access selected layer {layer_path:?}: {e:?}"),
}
}
};
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get(&id)) else {
return;
};
if clipboard == Clipboard::Device {
let mut buffer = Vec::new();
copy_val(&mut buffer);
let mut copy_text = String::from("graphite/layer: ");
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
let copy_val = |buffer: &mut Vec<CopyBufferEntry>| {
for layer_path in active_document.metadata().shallowest_unique_layers(active_document.metadata().selected_layers()) {
let Some(layer) = layer_path.last().copied() else {
continue;
};
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
} else {
let copy_buffer = &mut self.copy_buffer;
copy_buffer[clipboard as usize].clear();
copy_val(&mut copy_buffer[clipboard as usize]);
let node = layer.to_node();
let Some(node) = active_document.network().nodes.get(&node).and_then(|node| node.inputs.first()).and_then(|input| input.as_node()) else {
continue;
};
buffer.push(CopyBufferEntry {
nodes: NodeGraphMessageHandler::copy_nodes(
active_document.network(),
&active_document
.network()
.all_dependencies(node)
.enumerate()
.map(|(index, (_, node_id))| (node_id, index as NodeId))
.collect(),
)
.collect(),
selected: active_document.metadata().selected_layers_contains(layer),
collapsed: false,
});
}
};
if clipboard == Clipboard::Device {
let mut buffer = Vec::new();
copy_val(&mut buffer);
let mut copy_text = String::from("graphite/layer: ");
copy_text += &serde_json::to_string(&buffer).expect("Could not serialize paste");
responses.add(FrontendMessage::TriggerTextCopy { copy_text });
} else {
let copy_buffer = &mut self.copy_buffer;
copy_buffer[clipboard as usize].clear();
copy_val(&mut copy_buffer[clipboard as usize]);
}
}
PortfolioMessage::Cut { clipboard } => {
@@ -364,47 +382,20 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
}
}
// TODO: Paste message is unused, delete it?
PortfolioMessage::Paste { clipboard } => {
let shallowest_common_folder = self.active_document().map(|document| {
document
.document_legacy
.shallowest_common_folder(document.selected_layers())
.expect("While pasting, the selected layers did not exist while attempting to find the appropriate folder path for insertion")
});
if let Some(folder) = shallowest_common_folder {
responses.add(DocumentMessage::DeselectAllLayers);
responses.add(DocumentMessage::StartTransaction);
responses.add(PortfolioMessage::PasteIntoFolder {
clipboard,
folder_path: folder.to_vec(),
insert_index: -1,
});
responses.add(DocumentMessage::CommitTransaction);
}
}
PortfolioMessage::PasteIntoFolder {
clipboard,
folder_path: path,
insert_index,
} => {
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {
if let Some(document) = self.active_document() {
trace!("Pasting into folder {path:?} as index: {insert_index}");
let destination_path = [path.to_vec(), vec![generate_uuid()]].concat();
responses.add_front(DocumentMessage::UpdateLayerMetadata {
layer_path: destination_path.clone(),
layer_metadata: entry.layer_metadata,
});
document.load_layer_resources(responses);
responses.add_front(DocumentOperation::InsertLayer {
layer: Box::new(entry.layer.clone()),
destination_path,
if self.active_document().is_some() {
trace!("Pasting into folder {parent:?} as index: {insert_index}");
let id = generate_uuid();
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes: entry.nodes.clone(),
parent,
insert_index,
duplicating: false,
});
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
}
};
@@ -421,27 +412,23 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
PortfolioMessage::PasteSerializedData { data } => {
if let Some(document) = self.active_document() {
if let Ok(data) = serde_json::from_str::<Vec<CopyBufferEntry>>(&data) {
let shallowest_common_folder = document
.document_legacy
.shallowest_common_folder(document.selected_layers())
.expect("While pasting from serialized, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
let parent = document.metadata().deepest_common_ancestor(document.metadata().selected_layers());
responses.add(DocumentMessage::DeselectAllLayers);
responses.add(DocumentMessage::StartTransaction);
for entry in data.iter().rev() {
let destination_path = [shallowest_common_folder.to_vec(), vec![generate_uuid()]].concat();
for entry in data.into_iter().rev() {
document.load_layer_resources(responses);
responses.add(DocumentOperation::InsertLayer {
layer: Box::new(entry.layer.clone()),
destination_path: destination_path.clone(),
let id = generate_uuid();
responses.add(GraphOperationMessage::NewCustomLayer {
id,
nodes: entry.nodes,
parent,
insert_index: -1,
duplicating: false,
});
responses.add(DocumentMessage::UpdateLayerMetadata {
layer_path: destination_path,
layer_metadata: entry.layer_metadata,
});
if entry.selected {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
}
}
responses.add(DocumentMessage::CommitTransaction);
@@ -557,7 +544,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
Import,
NextDocument,
OpenDocument,
Paste,
PasteIntoFolder,
PrevDocument,
);
@@ -568,7 +554,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
if let Some(document) = self.active_document() {
if document.layer_metadata.values().any(|data| data.selected) {
if document.metadata().selected_layers().next().is_some() {
let select = actions!(PortfolioMessageDiscriminant;
Copy,
Cut,

View File

@@ -17,7 +17,7 @@ use std::collections::VecDeque;
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
let id = *layer_path.last().unwrap();
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths });
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![id] })
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] })
}
/// Create a new bitmap layer from an [`graphene_core::raster::ImageFrame<Color>`]
@@ -232,7 +232,7 @@ 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, u64)> {
self.node_graph.primary_flow_from_opt(Some(self.layer_node))
self.node_graph.primary_flow_from_node(Some(self.layer_node))
}
/// Does a node exist in the layer's primary flow

View File

@@ -248,9 +248,9 @@ impl PathToolData {
// We didn't find a point nearby, so consider selecting the nearest shape instead
else if let Some(layer) = document.metadata().click(input.mouse.position, &document.document_legacy.document_network) {
if shift {
responses.add(NodeGraphMessage::AddSelectNodes { nodes: vec![layer.to_node()] });
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
} else {
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![layer.to_node()] });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
}
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = input.mouse.position;
@@ -386,7 +386,7 @@ impl Fsm for PathToolFsmState {
let shift_pressed = input.keyboard.get(add_to_selection as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![] });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
@@ -401,7 +401,7 @@ impl Fsm for PathToolFsmState {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![] });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);

View File

@@ -369,7 +369,7 @@ impl SelectToolData {
}
// Select the originals
responses.add(NodeGraphMessage::SetSelectedNodes {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: originals.iter().map(|layer| layer.to_node()).collect::<Vec<_>>(),
});
@@ -714,13 +714,13 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.clear();
tool_data.layers_dragging.extend(replacement_selected_layers.iter());
responses.add(NodeGraphMessage::SetSelectedNodes {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: replacement_selected_layers.iter().map(|layer| layer.to_node()).collect(),
});
}
} else if let Some(selecting_layer) = tool_data.select_single_layer.take() {
if !tool_data.has_dragged {
responses.add(NodeGraphMessage::SetSelectedNodes {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: vec![selecting_layer.to_node()],
});
}
@@ -778,7 +778,7 @@ impl Fsm for SelectToolFsmState {
let quad = tool_data.selection_quad();
// For shallow select we don't update dragging layers until inside drag_start_shallowest_manipulation()
tool_data.layers_dragging = document.metadata().intersect_quad(quad, &document.document_legacy.document_network).collect();
responses.add_front(NodeGraphMessage::SetSelectedNodes {
responses.add_front(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
});
responses.add_front(DocumentMessage::Overlays(
@@ -906,7 +906,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
let new_selected = ancestor.unwrap_or_else(|| layer.child_of_root(document.metadata()));
tool_data.layers_dragging = vec![new_selected];
responses.add(NodeGraphMessage::SetSelectedNodes {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
});
// tool_data
@@ -916,7 +916,7 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, mut selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData) {
tool_data.layers_dragging.append(&mut selected);
responses.add(NodeGraphMessage::SetSelectedNodes {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
});
// tool_data
@@ -937,7 +937,7 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
return;
};
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![new_selected.to_node()] });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] });
}
fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document: &Document, responses: &mut VecDeque<Message>) {

View File

@@ -269,7 +269,7 @@ impl TextToolData {
self.set_editing(true, render_data, document, responses);
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: vec![self.layer.to_node()] });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![self.layer.to_node()] });
}
fn interact(&mut self, state: TextToolFsmState, mouse: DVec2, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) -> TextToolFsmState {
@@ -304,7 +304,7 @@ impl TextToolData {
self.set_editing(true, render_data, document, responses);
responses.add(NodeGraphMessage::SetSelectedNodes { nodes: self.layer.to_path() });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: self.layer.to_path() });
TextToolFsmState::Editing
} else {

View File

@@ -279,17 +279,6 @@ impl NodeRuntime {
let old_thumbnail = self.thumbnails.entry(node_id).or_default();
if *old_thumbnail != render.svg {
responses.add(FrontendMessage::UpdateDocumentLayerDetails {
data: LayerPanelEntry {
name: "Layer".to_string(),
tooltip: format!("Layer id: {node_id}"),
visible: true,
layer_type: LayerDataTypeDiscriminant::Layer,
layer_metadata: LayerMetadata::new(true),
path: vec![node_id],
thumbnail: render.svg.to_string(),
},
});
responses.add(FrontendMessage::UpdateNodeThumbnail {
id: node_id,
value: render.svg.to_string(),
@@ -516,6 +505,26 @@ impl NodeGraphExecutor {
new_upstream_transforms,
transform,
}) => {
for (&node_id, svg) in &new_thumbnails {
if !document.document_network.nodes.contains_key(&node_id) {
warn!("Missing node");
continue;
}
responses.add(FrontendMessage::UpdateDocumentLayerDetails {
data: LayerPanelEntry {
name: "Layer".to_string(),
tooltip: format!("Layer id: {node_id}"),
visible: true,
layer_type: LayerDataTypeDiscriminant::Layer,
layer_metadata: LayerMetadata {
expanded: true,
selected: document.metadata.selected_layers_contains(LayerNodeIdentifier::new(node_id, &document.document_network)),
},
path: vec![node_id],
thumbnail: svg.to_string(),
},
});
}
self.thumbnails = new_thumbnails;
document.metadata.update_transforms(new_transforms, new_upstream_transforms);
document.metadata.update_click_targets(new_click_targets);

View File

@@ -5,6 +5,7 @@
use crate::helpers::{translate_key, Error};
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use editor::application::generate_uuid;
use editor::application::Editor;
@@ -546,11 +547,9 @@ impl JsEditorHandle {
/// Move a layer to be next to the specified neighbor
#[wasm_bindgen(js_name = moveLayerInTree)]
pub fn move_layer_in_tree(&self, folder_path: Vec<LayerId>, insert_index: isize) {
let message = DocumentMessage::MoveSelectedLayersTo {
folder_path,
insert_index,
reverse_index: true,
};
let parent = folder_path.last().copied().map(LayerNodeIdentifier::new_unchecked).unwrap_or(LayerNodeIdentifier::ROOT);
let message = DocumentMessage::MoveSelectedLayersTo { parent, insert_index };
self.dispatch(message);
}
@@ -654,7 +653,7 @@ impl JsEditorHandle {
#[wasm_bindgen(js_name = selectNodes)]
pub fn select_nodes(&self, nodes: Option<Vec<u64>>) {
let nodes = nodes.unwrap_or_default();
let message = NodeGraphMessage::SetSelectedNodes { nodes };
let message = NodeGraphMessage::SelectedNodesSet { nodes };
self.dispatch(message);
}

View File

@@ -616,13 +616,23 @@ impl NodeNetwork {
FlowIter {
stack: self.outputs.iter().map(|output| output.node_id).collect(),
network: self,
primary: true,
}
}
pub fn primary_flow_from_opt(&self, id: Option<NodeId>) -> impl Iterator<Item = (&DocumentNode, u64)> {
pub fn primary_flow_from_node(&self, id: Option<NodeId>) -> impl Iterator<Item = (&DocumentNode, u64)> {
FlowIter {
stack: id.map_or_else(|| self.outputs.iter().map(|output| output.node_id).collect(), |id| vec![id]),
network: self,
primary: true,
}
}
pub fn all_dependencies(&self, id: NodeId) -> impl Iterator<Item = (&DocumentNode, u64)> {
FlowIter {
stack: vec![id],
network: self,
primary: false,
}
}
@@ -664,6 +674,7 @@ impl NodeNetwork {
struct FlowIter<'a> {
stack: Vec<NodeId>,
network: &'a NodeNetwork,
primary: bool,
}
impl<'a> Iterator for FlowIter<'a> {
type Item = (&'a DocumentNode, NodeId);
@@ -671,13 +682,11 @@ impl<'a> Iterator for FlowIter<'a> {
loop {
let node_id = self.stack.pop()?;
if let Some(document_node) = self.network.nodes.get(&node_id) {
self.stack.extend(
document_node
.inputs
.iter()
.take(1) // Only show the primary input
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
);
let inputs = document_node.inputs.iter().take(if self.primary { 1 } else { usize::MAX });
let node_ids = inputs.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(*node_id) } else { None });
self.stack.extend(node_ids);
return Some((document_node, node_id));
};
}