nodeAtPoint method
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;
}