fix: coalesce pinch updates and stop live zoom write-back
All checks were successful
CI / Windows build (push) Successful in 9m55s
All checks were successful
CI / Windows build (push) Successful in 9m55s
Surface Aug6 diag showed sDrop=0 but ~220 same-ms dual ZOOM frames and √2 cur ping-pong from reading currentZoom back into pinch state. Flush once per microtask and embed gitSha in diagnostic meta. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
// because this canvas always uses an infinite boundary, free pan, and no
|
||||
// rotation — so that code was provably a no-op here.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
@@ -122,6 +123,12 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
/// (the reported flicker). Normalizing kills that pop at the source.
|
||||
double _rawScaleAtBaseline = 1.0;
|
||||
|
||||
/// Windows ScaleGestureRecognizer emits one onUpdate per finger move in the
|
||||
/// same event-loop turn. Applying both mutates the matrix twice with an
|
||||
/// intermediate state (Surface diag: √2-ish cur ping-pong). Keep latest only.
|
||||
ScaleUpdateDetails? _pendingScaleUpdate;
|
||||
bool _scaleFlushScheduled = false;
|
||||
|
||||
// --- Matrix helpers (infinite boundary → no clamping to bounds) -----------
|
||||
|
||||
Matrix4 _matrixTranslate(Matrix4 matrix, Offset translation) {
|
||||
@@ -169,6 +176,8 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
_scaleAnimation?.removeListener(_handleScaleAnimation);
|
||||
_scaleAnimation = null;
|
||||
}
|
||||
_pendingScaleUpdate = null;
|
||||
_scaleFlushScheduled = false;
|
||||
_gestureType = null;
|
||||
_lastPointerCount = details.pointerCount;
|
||||
_scaleStart = _transformer.value.getMaxScaleOnAxis();
|
||||
@@ -178,6 +187,27 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
}
|
||||
|
||||
void _onScaleUpdate(ScaleUpdateDetails details) {
|
||||
// Pointer-count change must apply immediately (re-baseline), not coalesce.
|
||||
if (details.pointerCount != _lastPointerCount) {
|
||||
_pendingScaleUpdate = null;
|
||||
_scaleFlushScheduled = false;
|
||||
_applyScaleUpdate(details);
|
||||
return;
|
||||
}
|
||||
_pendingScaleUpdate = details;
|
||||
if (_scaleFlushScheduled) return;
|
||||
_scaleFlushScheduled = true;
|
||||
scheduleMicrotask(() {
|
||||
_scaleFlushScheduled = false;
|
||||
final pending = _pendingScaleUpdate;
|
||||
_pendingScaleUpdate = null;
|
||||
if (pending != null && mounted && _scaleStart != null) {
|
||||
_applyScaleUpdate(pending);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _applyScaleUpdate(ScaleUpdateDetails details) {
|
||||
final double scale = _transformer.value.getMaxScaleOnAxis();
|
||||
_scaleAnimationFocalPoint = details.localFocalPoint;
|
||||
|
||||
@@ -220,14 +250,20 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
final bool focalDrop =
|
||||
details.pointerCount >= 2 && focalJumpPx > _kFocalGlitchPx;
|
||||
|
||||
void record(double appliedChange, bool scaleDrop, bool focalDropped) {
|
||||
void record(
|
||||
double currentScale,
|
||||
double appliedChange,
|
||||
bool softClamped,
|
||||
bool focalDropped,
|
||||
) {
|
||||
InputDiagnostics.instance.recordScaleFrame(
|
||||
rawScale: details.scale,
|
||||
pointerCount: details.pointerCount,
|
||||
currentScale: scale,
|
||||
currentScale: currentScale,
|
||||
appliedChange: appliedChange,
|
||||
focalJumpPx: focalJumpPx,
|
||||
scaleDrop: scaleDrop,
|
||||
scaleDrop: false,
|
||||
softClamped: softClamped,
|
||||
focalDrop: focalDropped,
|
||||
);
|
||||
}
|
||||
@@ -252,7 +288,7 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
_rawScaleAtBaseline = details.scale;
|
||||
_lastAppliedScale = step.appliedScale;
|
||||
}
|
||||
record(1.0, step.spiked, true);
|
||||
record(_lastAppliedScale, 1.0, step.spiked, true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,7 +309,7 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
final double applied =
|
||||
_lastAppliedScale > 0 ? targetScale / _lastAppliedScale : 1.0;
|
||||
_lastAppliedScale = targetScale;
|
||||
record(applied, step.spiked, false);
|
||||
record(targetScale, applied, step.spiked, false);
|
||||
|
||||
case _GestureType.pan:
|
||||
assert(_referenceFocalPoint != null);
|
||||
@@ -281,7 +317,7 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
if (details.scale != 1.0) return;
|
||||
if (focalDrop) {
|
||||
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
|
||||
record(1.0, false, true);
|
||||
record(scale, 1.0, false, true);
|
||||
return;
|
||||
}
|
||||
final Offset translationChange =
|
||||
@@ -289,11 +325,17 @@ class _PenInteractiveViewerState extends State<PenInteractiveViewer>
|
||||
_transformer.value =
|
||||
_matrixTranslate(_transformer.value, translationChange);
|
||||
_referenceFocalPoint = _transformer.toScene(details.localFocalPoint);
|
||||
record(1.0, false, false);
|
||||
record(scale, 1.0, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void _onScaleEnd(ScaleEndDetails details) {
|
||||
final pending = _pendingScaleUpdate;
|
||||
_pendingScaleUpdate = null;
|
||||
_scaleFlushScheduled = false;
|
||||
if (pending != null && _scaleStart != null) {
|
||||
_applyScaleUpdate(pending);
|
||||
}
|
||||
_scaleStart = null;
|
||||
_referenceFocalPoint = null;
|
||||
_lastPointerCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user