duplicateSubtree static method
DuplicateSceneSubtreeResult
duplicateSubtree(
- CanvasSceneDocument doc,
- ElementId id, {
- Vec2 shift = const Vec2(16, 16),
Implementation
static DuplicateSceneSubtreeResult duplicateSubtree(
CanvasSceneDocument doc,
ElementId id, {
Vec2 shift = const Vec2(16, 16),
}) {
final node = findById(doc, id);
if (node == null) {
return (doc: doc, idMap: const <ElementId, ElementId>{}, primaryId: null);
}
final pr = findParentOf(doc, id);
if (pr == null) {
return (doc: doc, idMap: const <ElementId, ElementId>{}, primaryId: null);
}
// Seed the reservation set once from the existing document.
//
// The counter remains operation-global, matching the existing copy-ID
// scheme. Each accepted generated ID is immediately reserved so later
// cloned nodes cannot collide with it.
final reservedIds = collectAllNodeIds(doc: doc);
var copyCounter = 0;
ElementId nextCopyId(ElementId oldId) {
while (true) {
copyCounter++;
final candidate = '${oldId}_copy_$copyCounter';
if (reservedIds.add(candidate)) {
return candidate;
}
}
}
final idMap = <ElementId, ElementId>{};
Node clone(Node n) {
final newId = nextCopyId(n.id);
idMap[n.id] = newId;
return switch (n) {
final TextNode node => node.copyWith(id: newId),
final ImageNode node => node.copyWith(id: newId),
final PathNode node => node.copyWith(id: newId),
final IconNode node => node.copyWith(id: newId),
final GroupNode node => node.copyWith(
id: newId,
children: [for (final child in node.children) clone(child)],
),
};
}
// Clone subtree.
var cloned = clone(node);
// Shift root position only.
final xf0 = cloned.xf;
cloned = cloned.withXf(xf0.copyWith(position: xf0.position + shift));
// Insert immediately after the original.
// Later siblings paint above earlier siblings, so the duplicate appears
// immediately above the original in stack order.
final insertIndex = pr.index + 1;
CanvasSceneDocument out;
if (pr.parent == null) {
final kids = [...doc.children];
kids.insert(_clampInsertIndex(insertIndex, kids.length), cloned);
out = doc.copyWith(children: kids);
} else {
final kids = [...pr.parent!.childrenOrEmpty];
kids.insert(_clampInsertIndex(insertIndex, kids.length), cloned);
out = _replaceChildren(doc, pr.parent, kids);
}
return (doc: out, idMap: idMap, primaryId: cloned.id);
}