dismissedRect static method

Rect dismissedRect({
  1. required Rect restingRect,
  2. required double travel,
  3. required double screenHeight,
  4. double horizontalOffset = 0.0,
  5. Offset? scaleAnchor,
})

The frame a sheet occupies mid-swipe, in global coordinates — the morph's destination when a swipe is what closed the sheet.

restingRect is carried down by the damped fall and shrunk about the pivot.

scaleAnchor supplies that pivot: the grabbed point, expressed in the resting card's frame (the live finger position minus the raw fall). Carried down with the fall, it keeps the grabbed content exactly under the finger below the damping knee — the direct-manipulation feel of the native gesture. Omit it and the sheet shrinks about its own centre.

horizontalOffset only translates, never feeding the scale — measured off iOS 26, where a 143 pt sideways sweep changed the card's scale by just 0.029, all attributable to incidental vertical drift.

The card's bottom edge cannot lift into view under this pivot while dismissScaleGain stays below 1.0 per card height, because the fall always outruns the shrink; the closing clamp guards that invariant against a future retune.

Implementation

static Rect dismissedRect({
  required Rect restingRect,
  required double travel,
  required double screenHeight,
  double horizontalOffset = 0.0,
  Offset? scaleAnchor,
}) {
  final cardFraction =
      screenHeight <= 0.0 ? 0.0 : restingRect.height / screenHeight;
  final fall = Offset(
    0.0,
    dampedDismissTravel(travel, cardFraction: cardFraction) * screenHeight,
  );
  final fallen = restingRect.shift(fall);
  final scale = dismissScale(travel, cardFraction: cardFraction);
  final pivot = scaleAnchor == null
      ? fallen.center
      : scaleAnchor.translate(0.0, fall.dy);

  final scaled = Rect.fromLTRB(
    pivot.dx + (fallen.left - pivot.dx) * scale,
    pivot.dy + (fallen.top - pivot.dy) * scale,
    pivot.dx + (fallen.right - pivot.dx) * scale,
    pivot.dy + (fallen.bottom - pivot.dy) * scale,
  );
  // Invariant guard — see the doc comment. Never active at the shipped gain.
  final lift = restingRect.bottom - scaled.bottom;
  final grounded = lift > 0.0 ? scaled.shift(Offset(0.0, lift)) : scaled;

  return grounded.shift(Offset(horizontalOffset, 0.0));
}