computeDragRect function

Rect computeDragRect({
  1. required GestureBounds bounds,
  2. required Rect currentRect,
  3. required Rect originRect,
  4. required Rect displayRect,
  5. required double aspectRatio,
  6. required Offset focalPoint,
  7. required Offset focalPointDelta,
  8. required Rect startRect,
  9. required Offset startFocalPoint,
  10. required Offset anchorFn(
    1. AnchorContext
    ),
})

New rect for a DragGesture update. Free pan (no scaleResponse) is pure friction-damped translation; scaleResponse routes through the anchor pipeline (focal-preserving size + position).

Implementation

Rect computeDragRect({
  required GestureBounds bounds,
  required Rect currentRect,
  required Rect originRect,
  required Rect displayRect,
  required double aspectRatio,
  required Offset focalPoint,
  required Offset focalPointDelta,
  required Rect startRect,
  required Offset startFocalPoint,
  required Offset Function(AnchorContext) anchorFn,
}) {
  final baseRect = displayRect.baseRect(aspectRatio);
  final stateX = axisStateX(focalPointDelta.dx, currentRect, originRect, displayRect);
  final stateY = axisStateY(focalPointDelta.dy, currentRect, originRect, displayRect);
  final effectiveDeltaX = bounds.hasHorizontalBound
      ? bounds.friction(stateX, focalPointDelta.dx)
      : focalPointDelta.dx;
  final effectiveDeltaY = bounds.hasVerticalBound
      ? bounds.friction(stateY, focalPointDelta.dy)
      : focalPointDelta.dy;

  // Fast path: no scaleResponse anywhere → pure friction-scaled
  // translation. The unified anchor pipeline below reduces to this
  // mathematically when `prevScale == combinedFactor == 1`, but a
  // stale `startRect.width` from a prior gesture can flip `prevScale`
  // off 1 and introduce an unwanted anchor offset on a free-pan
  // gesture (e.g. browse-card swipe).
  if (!bounds.hasScaleResponse) {
    return currentRect.translate(effectiveDeltaX, effectiveDeltaY);
  }

  // Anchor pipeline. scaleInput collapses to `currentRect.center +
  // effectiveDelta`; the anchor only survives in [newCenter] where
  // it's multiplied by `(prevScale − combinedFactor)` — the focal-
  // preserving adjustment for this frame's scale change.
  final scaleInputX = currentRect.center.dx + effectiveDeltaX;
  final scaleInputY = currentRect.center.dy + effectiveDeltaY;
  final factorX = bounds.hasHorizontalScaleResponse
      ? scaleForCenter(
          center: scaleInputX,
          basePos: baseRect.center.dx,
          baseHalfDim: baseRect.width / 2,
          displayLow: displayRect.left,
          displayHigh: displayRect.right,
          response: bounds[stateX.activeBound]?.scaleResponse,
        )
      : 1.0;
  final factorY = bounds.hasVerticalScaleResponse
      ? scaleForCenter(
          center: scaleInputY,
          basePos: baseRect.center.dy,
          baseHalfDim: baseRect.height / 2,
          displayLow: displayRect.top,
          displayHigh: displayRect.bottom,
          response: bounds[stateY.activeBound]?.scaleResponse,
        )
      : 1.0;
  final combinedFactor = factorX * factorY;
  final anchor = startFocalPoint - startRect.center;
  final prevScale = startRect.width == 0 ? 1.0 : currentRect.width / startRect.width;
  final scaleDelta = prevScale - combinedFactor;
  final newCenterX = scaleInputX + anchor.dx * scaleDelta;
  final newCenterY = scaleInputY + anchor.dy * scaleDelta;
  return applyDragTransform(
    newCenter: Offset(newCenterX, newCenterY),
    baseRect: baseRect,
    aspectRatio: aspectRatio,
    scaleFactor: combinedFactor,
  );
}