duplicateSubtree static method

DuplicateSceneSubtreeResult duplicateSubtree(
  1. CanvasSceneDocument doc,
  2. ElementId id, {
  3. 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);
}