fix: soft-clamp pinch zoom and Krita-inspired brush opacity
All checks were successful
CI / Windows build (push) Successful in 10m28s
All checks were successful
CI / Windows build (push) Successful in 10m28s
Hard SDROP avalanches froze lastRaw while zoom still crawled; soft-clamp and re-anchor instead. Ballpoint is near-solid, pencil uses soft √p without multiply stacking; PDF ink falls back to nearest page during zoom settle. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,6 +18,12 @@
|
||||
// pinch has accumulated (e.g. 0.40) — multiplying the fresh `scaleStart` by
|
||||
// that stale 0.40 popped the zoom to a wrong scale and snapped back (the
|
||||
// reported flicker). Normalizing against `rawScaleAtBaseline` removes the pop.
|
||||
//
|
||||
// Soft-clamp (Surface 2026-08-05 diag): a HARD drop of frames whose per-step
|
||||
// ratio exceeds the glitch band caused an avalanche — lastRaw never advanced,
|
||||
// so every subsequent frame also dropped while pdfrx/live zoom still crawled.
|
||||
// [softClampedPinchStep] always returns an applied scale, clamping the step,
|
||||
// and tells the caller to re-anchor when a spike was clipped.
|
||||
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
|
||||
@@ -39,3 +45,59 @@ double absolutePinchScale({
|
||||
rawScaleAtBaseline > 0 ? rawScale / rawScaleAtBaseline : 1.0;
|
||||
return clampDouble(scaleStart * cumulative, minScale, maxScale);
|
||||
}
|
||||
|
||||
/// Result of one soft-clamped pinch step.
|
||||
class SoftPinchStep {
|
||||
const SoftPinchStep({
|
||||
required this.appliedScale,
|
||||
required this.reanchor,
|
||||
required this.spiked,
|
||||
});
|
||||
|
||||
/// Scale to write into the matrix / controller this frame.
|
||||
final double appliedScale;
|
||||
|
||||
/// When true the caller must set `scaleStart = appliedScale` and
|
||||
/// `rawScaleAtBaseline = rawScale` so absolute tracking does not keep
|
||||
/// fighting the clamp on later frames.
|
||||
final bool reanchor;
|
||||
|
||||
/// True when the ideal absolute target was clipped by the per-step band.
|
||||
final bool spiked;
|
||||
}
|
||||
|
||||
/// Soft-clamp the per-frame scale change instead of dropping the frame.
|
||||
///
|
||||
/// Ideal scale comes from [absolutePinchScale]. The step from
|
||||
/// [lastAppliedScale] is then limited to `[1/maxStepRatio, maxStepRatio]`.
|
||||
/// Spikes still get partially applied (smooth catch-up) and the caller
|
||||
/// re-anchors so the next frame starts clean.
|
||||
SoftPinchStep softClampedPinchStep({
|
||||
required double scaleStart,
|
||||
required double rawScaleAtBaseline,
|
||||
required double rawScale,
|
||||
required double lastAppliedScale,
|
||||
required double minScale,
|
||||
required double maxScale,
|
||||
required double maxStepRatio,
|
||||
}) {
|
||||
final ideal = absolutePinchScale(
|
||||
scaleStart: scaleStart,
|
||||
rawScaleAtBaseline: rawScaleAtBaseline,
|
||||
rawScale: rawScale,
|
||||
minScale: minScale,
|
||||
maxScale: maxScale,
|
||||
);
|
||||
if (lastAppliedScale <= 0 || maxStepRatio <= 1.0) {
|
||||
return SoftPinchStep(appliedScale: ideal, reanchor: false, spiked: false);
|
||||
}
|
||||
final lo = lastAppliedScale / maxStepRatio;
|
||||
final hi = lastAppliedScale * maxStepRatio;
|
||||
final applied = clampDouble(ideal, lo, hi);
|
||||
final spiked = applied != ideal;
|
||||
return SoftPinchStep(
|
||||
appliedScale: clampDouble(applied, minScale, maxScale),
|
||||
reanchor: spiked,
|
||||
spiked: spiked,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user