fix(zoom): stop re-baseline scale oscillation

Device log showed the applied scale oscillating ~1.4x every frame while
the raw pinch was smooth (cur 1.116->0.797->1.074, raw ~0.46). Root cause:
on a pointer-count re-baseline (Windows touch flickers 2<->1<->2 mid-pinch)
the code set _scaleStart = matrix.getMaxScaleOnAxis() — a read-back captured
at a glitchy instant — so the absolute map K = scaleStart/rawScaleAtBaseline
jumped frame to frame.

Fix: anchor the re-baseline to the CLEAN tracked _lastAppliedScale instead
of the live matrix read-back, so the displayed scale is continuous across
the re-baseline regardless of any matrix transient. The math is already
covered by the pinch_scale_solver "same-instant re-baseline" test; this just
feeds it the right value.

flutter analyze: 0. pinch_scale_solver + pen_zoom: pass.
This commit is contained in:
2026-06-23 16:52:14 +08:00
parent 0ae2671f9b
commit eae4493954

View File

@@ -194,13 +194,20 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
// The transitional frame itself is skipped.
if (details.pointerCount != _lastPointerCount) {
_lastPointerCount = details.pointerCount;
_scaleStart = _transformer.value.getMaxScaleOnAxis();
// Anchor the new baseline to the CLEAN tracked scale (_lastAppliedScale),
// NOT a fresh matrix read-back. Windows touch flickers the pointer count
// (2↔1↔2) mid-pinch, firing this re-baseline spuriously; reading
// getMaxScaleOnAxis() at that glitchy instant popped _scaleStart to a
// noisy value, so the absolute map K = scaleStart / rawScaleAtBaseline
// oscillated frame-to-frame (the reported "zoom jump"). Using
// _lastAppliedScale makes the displayed scale CONTINUOUS across the
// re-baseline: target == _lastAppliedScale at this instant, regardless of
// any transient in the live matrix.
_scaleStart = _lastAppliedScale;
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
_lastRawScale = details.scale;
_lastAppliedScale = _scaleStart!;
// Re-anchor the absolute mapping: from here, cumulative scale is measured
// relative to THIS frame's details.scale (so the next good frame starts
// from _scaleStart, not _scaleStart * a stale cumulative value).
// Re-anchor the cumulative scale to THIS frame's details.scale so the next
// good frame resumes from _scaleStart (not _scaleStart × a stale ratio).
_rawScaleAtBaseline = details.scale;
InputDiagnostics.instance.recordRebaseline();
return;