nodeAtPoint method

InspectNode? nodeAtPoint(
  1. double x,
  2. double y
)

The deepest node whose box contains (x, y), in the guest's own coordinates.

An approximation of a hit test, deliberately. It knows only rectangles: not transforms, not clips, not opacity, not IgnorePointer, and of two overlapping boxes at the same depth it takes the later, which is a guess at paint order rather than knowledge of it.

That is the right trade for a pointer. Following the mouse means answering every frame, where a round trip per move would stutter and being one node out for a moment costs nothing. Anything that has to be right — what a click actually selected — asks the guest, which runs the framework's own hitTest over the real render tree.

Nodes with no box are skipped rather than treated as empty: a provider or a builder lays nothing out, and its child is the thing under the cursor. Every node is considered rather than only the children of one that contains the point, because a child can be laid out beyond its parent — which is what an overflow is, and an overflowing widget is exactly what a pointer tends to be aimed at.

Offstage nodes are skipped too, subtree and all: a route kept alive under the current one holds its old rects, which overlap the screen — and being deeper, they won here, so picking on a screenshot could select a widget from the previous screen. What is not on the picture cannot be what the pointer means.

Implementation

InspectNode? nodeAtPoint(double x, double y) {
  InspectNode? best;
  var bestDepth = -1;
  for (var node in _onstage) {
    var layout = node.layout;
    if (layout == null) continue;
    if (x < layout.x || y < layout.y) continue;
    if (x >= layout.x + layout.width || y >= layout.y + layout.height) {
      continue;
    }
    // `>=` rather than `>`: depth-first order visits later siblings last, so
    // ties go to whichever was drawn on top.
    var depth = node.id.isEmpty ? 0 : node.id.split('/').length;
    if (depth >= bestDepth) {
      best = node;
      bestDepth = depth;
    }
  }
  return best;
}