mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Add Alt+drag layer duplication to the Layers panel (#4216)
* Add Alt+drag layer duplication to the Layers panel * Code review
This commit is contained in:
@@ -60,6 +60,10 @@ pub enum DocumentMessage {
|
||||
context: OverlayContext,
|
||||
},
|
||||
DuplicateSelectedLayers,
|
||||
DuplicateSelectedLayersTo {
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
EnterNestedNetwork {
|
||||
node_id: NodeId,
|
||||
},
|
||||
|
||||
@@ -449,37 +449,88 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &SelectedNodes(vec![layer.to_node()]), parent)
|
||||
});
|
||||
|
||||
for layer in layers.into_iter().rev() {
|
||||
let Some(parent) = layer.parent(self.metadata()) else { continue };
|
||||
for original_layer in layers.into_iter().rev() {
|
||||
let Some(parent) = original_layer.parent(self.metadata()) else { continue };
|
||||
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &SelectedNodes(vec![original_layer.to_node()]), parent);
|
||||
|
||||
// Copy the layer
|
||||
let mut copy_ids = HashMap::new();
|
||||
let node_id = layer.to_node();
|
||||
copy_ids.insert(node_id, NodeId(0));
|
||||
|
||||
self.network_interface
|
||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
|
||||
.enumerate()
|
||||
.for_each(|(index, node_id)| {
|
||||
copy_ids.insert(node_id, NodeId((index + 1) as u64));
|
||||
});
|
||||
|
||||
let nodes = self.network_interface.copy_nodes(©_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
|
||||
|
||||
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &SelectedNodes(vec![layer.to_node()]), parent);
|
||||
|
||||
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
|
||||
|
||||
let layer_id = *new_ids.get(&NodeId(0)).expect("Node Id 0 should be a layer");
|
||||
let layer = LayerNodeIdentifier::new_unchecked(layer_id);
|
||||
new_dragging.push(layer);
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack { layer, parent, insert_index });
|
||||
let Some(new_layer) = self.duplicate_layer(original_layer, responses) else { continue };
|
||||
new_dragging.push(new_layer);
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||
layer: new_layer,
|
||||
parent,
|
||||
insert_index,
|
||||
});
|
||||
}
|
||||
let nodes = new_dragging.iter().map(|layer| layer.to_node()).collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
DocumentMessage::DuplicateSelectedLayersTo { parent, insert_index } => {
|
||||
if !self.selection_network_path.is_empty() {
|
||||
log::error!("Duplicating selected layers is only supported for the document network");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mirror the placement constraints enforced when moving layers so a copy can't land somewhere a move couldn't
|
||||
let any_artboards = self
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers(self.metadata())
|
||||
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||
if any_artboards && parent != LayerNodeIdentifier::ROOT_PARENT {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_any_non_artboards = self
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers(self.metadata())
|
||||
.any(|layer| !self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||
let top_level_artboards = LayerNodeIdentifier::ROOT_PARENT
|
||||
.children(self.metadata())
|
||||
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||
if selected_any_non_artboards && parent == LayerNodeIdentifier::ROOT_PARENT && top_level_artboards {
|
||||
return;
|
||||
}
|
||||
|
||||
let layers_to_duplicate = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
|
||||
if layers_to_duplicate.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
|
||||
let mut new_layers = Vec::new();
|
||||
for layer in layers_to_duplicate {
|
||||
let Some(new_layer) = self.duplicate_layer(layer, responses) else { continue };
|
||||
|
||||
// Insert each copy one slot below the previous so the duplicates keep their original top-to-bottom order
|
||||
let placement_index = insert_index + new_layers.len();
|
||||
new_layers.push(new_layer);
|
||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||
layer: new_layer,
|
||||
parent,
|
||||
insert_index: placement_index,
|
||||
});
|
||||
|
||||
// Compensate the local transform so a copy dropped into a differently-transformed parent stays put in world space
|
||||
if layer.parent(self.metadata()) != Some(parent) {
|
||||
let layer_world_transform = self.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let undo_parent_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
||||
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: new_layer,
|
||||
transform: undo_parent_transform * layer_world_transform,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let nodes = new_layers.iter().map(|layer| layer.to_node()).collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
DocumentMessage::EnterNestedNetwork { node_id } => {
|
||||
self.breadcrumb_network_path.push(node_id);
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
@@ -2218,6 +2269,30 @@ impl DocumentMessageHandler {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Copies `layer` together with its full upstream node chain, queueing the new nodes with freshly minted IDs.
|
||||
/// Returns the new layer's identifier; the caller places it into the stack with a `MoveLayerToStack` response.
|
||||
fn duplicate_layer(&mut self, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> Option<LayerNodeIdentifier> {
|
||||
let mut copy_ids = HashMap::new();
|
||||
copy_ids.insert(layer.to_node(), NodeId(0));
|
||||
self.network_interface
|
||||
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::LayerChildrenUpstreamFlow)
|
||||
.enumerate()
|
||||
.for_each(|(index, node_id)| {
|
||||
copy_ids.insert(node_id, NodeId((index + 1) as u64));
|
||||
});
|
||||
|
||||
let nodes = self.network_interface.copy_nodes(©_ids, &[]).collect::<Vec<(NodeId, NodeTemplate)>>();
|
||||
let new_ids: HashMap<_, _> = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
|
||||
|
||||
let Some(&new_layer_id) = new_ids.get(&NodeId(0)) else {
|
||||
log::error!("Could not duplicate layer because its root node copy is missing");
|
||||
return None;
|
||||
};
|
||||
responses.add(NodeGraphMessage::AddNodes { nodes, new_ids });
|
||||
|
||||
Some(LayerNodeIdentifier::new_unchecked(new_layer_id))
|
||||
}
|
||||
|
||||
pub fn group_layers(
|
||||
responses: &mut VecDeque<Message>,
|
||||
insert_index: usize,
|
||||
|
||||
Reference in New Issue
Block a user