// lib/editor/canvas/pinch_scale_solver.dart // // Pure math for the pen canvas's absolute pinch-zoom. Extracted so the // re-baseline behavior (the subtle part) can be unit-tested without simulating // a flaky multi-pointer gesture. // // The pinch is driven ABSOLUTELY: the scale shown is always // scaleStart * (rawScale / rawScaleAtBaseline) // where `scaleStart` is the matrix scale captured at the current baseline and // `rawScaleAtBaseline` is the recognizer's cumulative `details.scale` at that // same baseline. Dividing by `rawScaleAtBaseline` re-normalizes the cumulative // scale so it reads 1.0 at the baseline instant. // // Why this matters: a baseline is captured at gesture start AND on every // pointer-count change (a finger blips 2→1→2, routine on Windows touch). At // gesture start `details.scale` is 1.0, so a naive `scaleStart * rawScale` is // correct. But at a MID-GESTURE re-baseline `details.scale` is whatever the // 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. import 'package:flutter/foundation.dart' show clampDouble; /// Returns the absolute target scale for a pinch frame. /// /// [scaleStart] — matrix scale captured at the current baseline. /// [rawScaleAtBaseline] — recognizer cumulative `details.scale` at that /// baseline (1.0 at gesture start; the live value at a re-baseline). /// [rawScale] — the recognizer's current cumulative `details.scale`. /// Result is clamped to [minScale, maxScale]. double absolutePinchScale({ required double scaleStart, required double rawScaleAtBaseline, required double rawScale, required double minScale, required double maxScale, }) { final double cumulative = rawScaleAtBaseline > 0 ? rawScale / rawScaleAtBaseline : 1.0; return clampDouble(scaleStart * cumulative, minScale, maxScale); }