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:
Keavon Chambers
2026-06-08 21:08:16 -07:00
committed by GitHub
parent 04bca4a877
commit bc1f63ccc6
4 changed files with 123 additions and 28 deletions

View File

@@ -60,6 +60,10 @@ pub enum DocumentMessage {
context: OverlayContext,
},
DuplicateSelectedLayers,
DuplicateSelectedLayersTo {
parent: LayerNodeIdentifier,
insert_index: usize,
},
EnterNestedNetwork {
node_id: NodeId,
},

View File

@@ -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(&copy_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(&copy_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,