releaseFromStateY function
VerticalRelease
releaseFromStateY({
- required Rect currentRect,
- required Rect displayRect,
- required GestureBounds bounds,
- required double velocity,
- Rect? projectedRect,
Computes the VerticalRelease plan for the Y axis given gesture-end state.
projectedRect is the rect at its post-scale-settle dimensions — used
to compute the viewport-fit center. Re-evaluated at gesture-end and
decay-end so the rubber target tracks where the rect actually lands.
See releaseFromStateX for full semantics.
Implementation
VerticalRelease releaseFromStateY({
required Rect currentRect,
required Rect displayRect,
required GestureBounds bounds,
required double velocity,
Rect? projectedRect,
}) {
final halfHeight = currentRect.height / 2;
final pos = currentRect.center.dy;
final dispTop = displayRect.top;
final dispBottom = displayRect.bottom;
final dispCenter = displayRect.center.dy;
// Size-aware past bounds — see [releaseFromStateX] for the rationale.
final double pastTopBound;
final double pastBottomBound;
if (currentRect.height > displayRect.height) {
pastTopBound = dispBottom - halfHeight;
pastBottomBound = dispTop + halfHeight;
} else {
pastTopBound = dispTop + halfHeight;
pastBottomBound = dispBottom - halfHeight;
}
final topDc = bounds[.top]?.decay ?? _defaultDecay;
final bottomDc = bounds[.bottom]?.decay ?? _defaultDecay;
final fitWidth = projectedRect?.width ?? currentRect.width;
final fitHeight = projectedRect?.height ?? currentRect.height;
// Scale-aware center adjustment — see [releaseFromStateX] for the
// rationale. Keeps the display-center point under display-center after
// the scale settles, with cover-clamp applied as a safety.
final heightRatio = currentRect.height == 0 ? 1.0 : fitHeight / currentRect.height;
double scaleAwareCenter(double cy) =>
displayRect.center.dy + (cy - displayRect.center.dy) * heightRatio;
double fitAt(double cy) {
return Rect.fromCenter(
center: Offset(currentRect.center.dx, scaleAwareCenter(cy)),
width: fitWidth,
height: fitHeight,
).getLimitedCenterYInside(displayRect);
}
final fit = fitAt(pos);
VerticalZone zoneOf(double p) {
if (p < pastTopBound) return .pastTop;
if (p > pastBottomBound) return .pastBottom;
if (p <= dispCenter) return .top;
return .bottom;
}
final startZone = zoneOf(pos);
if (velocity.abs() <= _velocityFloor) {
if ((pos - fit).abs() < 0.5) {
return VerticalRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
);
}
final settleConfig = (pos > fit ? bottomDc : topDc).settle;
return VerticalRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
settle: _rubberFling(startPos: pos, startVel: velocity, targetPos: fit, settle: settleConfig),
);
}
Decay? decayAt(VerticalZone zone, bool ttb) {
final (isTop, isPast) = switch (zone) {
.pastTop => (true, true),
.top => (true, false),
.bottom => (false, false),
.pastBottom => (false, true),
};
final dc = isTop ? topDc : bottomDc;
final extending = (isTop && !ttb) || (!isTop && ttb);
if (isPast) {
return extending ? dc.extendingPastDisplay : dc.retractingPastDisplay;
}
return extending ? dc.extending : dc.retracting;
}
double? exitBoundaryAt(VerticalZone zone, bool ttb) {
if (ttb) {
switch (zone) {
case .pastTop: return pastTopBound;
case .top: return dispCenter;
case .bottom: return pastBottomBound;
case .pastBottom: return null;
}
} else {
switch (zone) {
case .pastBottom: return pastBottomBound;
case .bottom: return dispCenter;
case .top: return pastTopBound;
case .pastTop: return null;
}
}
}
final phases = <AxisFling>[];
var p = pos;
var v = velocity;
var endPos = pos;
for (var i = 0; i < _maxPhases; i++) {
if (v.abs() <= _velocityFloor) break;
final ttb = v > 0;
final zone = zoneOf(p);
final pick = decayAt(zone, ttb);
if (pick == null) break;
final step = _runPhase(
pos: p,
vel: v,
decay: pick,
exitBoundary: exitBoundaryAt(zone, ttb),
);
phases.add(step.fling);
endPos = step.endPos;
v = step.endVel;
if (step.stopped) break;
p = step.endPos + v.sign * 0.01;
}
final endZone = zoneOf(endPos);
final VerticalDir direction = velocity > 0 ? .ttb : .btt;
final endFit = fitAt(endPos);
AxisFling? settle;
if (endZone case .pastTop) {
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: endFit, settle: topDc.settle);
} else if (endZone case .pastBottom) {
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: endFit, settle: bottomDc.settle);
} else if ((endPos - endFit).abs() >= 0.5) {
final settleConfig = (endPos > endFit ? bottomDc : topDc).settle;
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: endFit, settle: settleConfig);
}
return VerticalRelease(
direction: direction,
startZone: startZone,
endZone: endZone,
decay: phases,
settle: settle,
);
}