pickTopAtScene function

Node? pickTopAtScene(
  1. CanvasSceneDocument doc,
  2. Vec2 worldPos, {
  3. required ComputedScene computed,
  4. bool includeLocked = false,
  5. bool selectLeaf = false,
  6. Set<NodeId> ignoreIds = const {},
  7. NodeHitTest? extraFilter,
  8. double viewportZoom = 1.0,
  9. double minPathHitSlopScreenPx = 6.0,
})

Pick the topmost node at worldPos (canvas/world space).

Selection rule:

  • default: select nearest group ancestor (LogoNode/GroupNode) if selectable
  • selectLeaf=true: select the leaf if selectable (else fallback to group)

Filtering:

  • locked filtering applies to what you RETURN (unless includeLocked=true)
  • ignoreIds is subtree-aware via DrawItem.groupStack

Picking semantics:

  • fill-area hit follows path geometry; open subpaths are treated as implicitly closed for containment
  • vector picking is selection-oriented and may be more permissive than exact painted output
  • paths/icons also use outline proximity
  • thin strokes get a minimum screen-space hit slop

Implementation

Node? pickTopAtScene(
  CanvasSceneDocument doc, // kept for API symmetry; not used internally
  Vec2 worldPos, {
  required ComputedScene computed,
  bool includeLocked = false,
  bool selectLeaf = false,
  Set<NodeId> ignoreIds = const {},
  NodeHitTest? extraFilter,
  double viewportZoom = 1.0,
  double minPathHitSlopScreenPx = 6.0,
}) {
  final drawList = computed.drawList;

  // Topmost first: drawList is back-to-front paint order.
  for (var i = drawList.length - 1; i >= 0; i--) {
    final item = drawList[i];
    final leafId = item.leafId;

    // Subtree-aware ignore: if any ancestor group is ignored, skip this leaf.
    if (_anyGroupIgnored(item, ignoreIds)) continue;
    if (ignoreIds.contains(leafId)) continue;

    final leaf = computed.nodeById[leafId];
    if (leaf == null) continue;

    if (!includeLocked && leaf.locked) continue;
    if (extraFilter != null && !extraFilter(leaf)) continue;

    if (!_hitLeafAt(
      leafId,
      leaf,
      worldPos,
      computed,
      viewportZoom: viewportZoom,
      minPathHitSlopScreenPx: minPathHitSlopScreenPx,
    )) {
      continue;
    }

    final picked = _choosePickedNode(
      leaf: leaf,
      groupStack: item.groupStack,
      computed: computed,
      ignoreIds: ignoreIds,
      includeLocked: includeLocked,
      preferLeaf: selectLeaf,
    );

    if (picked != null) return picked;
  }

  return null;
}