dispatchTap method
Dispatch a synthetic tap at (x, y) through Flutter's
GestureBinding pointer pipeline — the same path a real pointer
takes.
Dispatches a synthetic tap at (x, y).
holdMs is the gap between down and up. A long-press recogniser fires on
duration alone, so a press is this same path with a longer rest — no
second tool, and no second code path to keep in step with this one.
buttons selects the pointer button (kPrimaryButton by default;
kSecondaryButton is the right-click / context-menu gesture). Passing it
is the only way to reach a secondaryTap handler, which is what a
context menu is usually hung off.
Implementation
Future<void> dispatchTap(
double x,
double y, {
int holdMs = 40,
int buttons = kPrimaryButton,
}) async {
final binding = GestureBinding.instance;
final position = Offset(x, y);
final now = SchedulerBinding.instance.currentSystemFrameTimeStamp;
final pointer = _nextPointer++;
final kind = _pointerKind();
// Explicit hit-test → dispatchEvent so the event traverses the same
// widget chain a real pointer would. Calling `handlePointerEvent`
// alone does NOT add the pointer to the binding's `_hitTests` map, so
// the matching `up` lands without a target and `onTap` never fires.
// Two-step (hitTest + dispatchEvent) matches Flutter's
// PointerEventConverter pipeline.
final downEvent = PointerDownEvent(
timeStamp: now,
pointer: pointer,
position: position,
kind: kind,
buttons: buttons,
);
final hitResult = HitTestResult();
// Load-bearing two-step (hitTest + dispatchEvent). `hitTest` is
// deprecated in favour of the view-scoped variant, but the single-view
// form is exactly what the studio recipe relies on and switching would
// change the pipeline; keep it and silence the info.
// ignore: deprecated_member_use
binding.hitTest(hitResult, position);
binding.dispatchEvent(downEvent, hitResult);
await Future<void>.delayed(Duration(milliseconds: holdMs));
binding.dispatchEvent(
PointerUpEvent(
timeStamp: now + Duration(milliseconds: holdMs),
pointer: pointer,
position: position,
kind: kind,
),
hitResult,
);
}