mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
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:
@@ -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),
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user