releaseFromStateScale function
ScaleRelease
releaseFromStateScale({
- required double width,
- required double baseWidth,
- required ShrinkBounds? shrink,
- required ExpandBounds? expand,
- required double velocity,
Computes the ScaleRelease plan given gesture-end scale-axis state.
width is the current rect width; baseWidth is the rest width
(scale = 1.0). shrink/expand hold the configured minScale/maxScale plus
their decay configs. velocity is in width-units per second.
Implementation
ScaleRelease releaseFromStateScale({
required double width,
required double baseWidth,
required ShrinkBounds? shrink,
required ExpandBounds? expand,
required double velocity,
}) {
if (baseWidth <= 0) {
return const ScaleRelease(
direction: .idle,
startZone: .shrink,
endZone: .shrink,
);
}
// Effective scale-axis boundaries. Null thresholds mean "no past zone on
// that side" — modeled with an out-of-reach width.
final shrinkLow = shrink?.minScale != null ? shrink!.minScale! * baseWidth : -baseWidth * 100;
final expandHigh = expand?.maxScale != null ? expand!.maxScale! * baseWidth : baseWidth * 100;
final dispCenter = baseWidth;
final shrinkDc = shrink?.decay ?? _defaultDecay;
final expandDc = expand?.decay ?? _defaultDecay;
ScaleZone zoneOf(double w) {
if (w < shrinkLow) return .pastShrink;
if (w > expandHigh) return .pastExpand;
if (w <= dispCenter) return .shrink;
return .expand;
}
final startZone = zoneOf(width);
if (velocity.abs() <= _velocityFloor) {
if (width < shrinkLow) {
return ScaleRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
settle: _rubberFling(startPos: width, startVel: velocity, targetPos: shrinkLow, settle: shrinkDc.settle),
);
}
if (width > expandHigh) {
return ScaleRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
settle: _rubberFling(startPos: width, startVel: velocity, targetPos: expandHigh, settle: expandDc.settle),
);
}
// Expanded within max → stay zoomed (X/Y will shift-to-fit).
if (width >= dispCenter - 0.5) {
return ScaleRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
);
}
// Shrunk in display → snap up to base.
return ScaleRelease(
direction: .idle,
startZone: startZone,
endZone: startZone,
settle: _rubberFling(startPos: width, startVel: velocity, targetPos: dispCenter, settle: shrinkDc.settle),
);
}
Decay? decayAt(ScaleZone zone, bool outward) {
final (isShrink, isPast) = switch (zone) {
.pastShrink => (true, true),
.shrink => (true, false),
.expand => (false, false),
.pastExpand => (false, true),
};
final dc = isShrink ? shrinkDc : expandDc;
final extending = (isShrink && !outward) || (!isShrink && outward);
if (isPast) {
return extending ? dc.extendingPastDisplay : dc.retractingPastDisplay;
}
return extending ? dc.extending : dc.retracting;
}
double? exitBoundaryAt(ScaleZone zone, bool outward) {
if (outward) {
switch (zone) {
case .pastShrink: return shrinkLow;
case .shrink: return dispCenter;
case .expand: return expandHigh;
case .pastExpand: return null;
}
} else {
switch (zone) {
case .pastExpand: return expandHigh;
case .expand: return dispCenter;
case .shrink: return shrinkLow;
case .pastShrink: return null;
}
}
}
final phases = <AxisFling>[];
var w = width;
var v = velocity;
var endPos = width;
for (var i = 0; i < _maxPhases; i++) {
if (v.abs() <= _velocityFloor) break;
final outward = v > 0;
final zone = zoneOf(w);
final pick = decayAt(zone, outward);
if (pick == null) break;
final step = _runPhase(
pos: w,
vel: v,
decay: pick,
exitBoundary: exitBoundaryAt(zone, outward),
);
phases.add(step.fling);
endPos = step.endPos;
v = step.endVel;
if (step.stopped) break;
w = step.endPos + v.sign * 0.01;
}
final endZone = zoneOf(endPos);
final ScaleDir direction = velocity > 0 ? .outward : .inward;
// Settle:
// - past shrink → rubber to shrinkLow (min cap).
// - past expand → rubber to expandHigh (max cap).
// - shrunk in display → snap up to base.
// - expanded in display or at base → stay (X/Y shift-to-fit if needed).
AxisFling? settle;
if (endZone case .pastShrink) {
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: shrinkLow, settle: shrinkDc.settle);
} else if (endZone case .pastExpand) {
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: expandHigh, settle: expandDc.settle);
} else if (endPos < dispCenter - 0.5) {
settle = _rubberFling(startPos: endPos, startVel: v, targetPos: dispCenter, settle: shrinkDc.settle);
}
return ScaleRelease(
direction: direction,
startZone: startZone,
endZone: endZone,
decay: phases,
settle: settle,
);
}