dispatchTap method

Future<void> dispatchTap(
  1. double x,
  2. double y, {
  3. int holdMs = 40,
  4. int buttons = kPrimaryButton,
})

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,
  );
}