feat(pen): forked InteractiveViewer (stylus-exclusive draw) + zoom diagnostic
All checks were successful
CI / Windows build (push) Successful in 18m35s
All checks were successful
CI / Windows build (push) Successful in 18m35s
Replace stock InteractiveViewer with PenInteractiveViewer, a focused fork of Flutter 3.44's InteractiveViewer for our config (constrained=false, infinite boundary, no rotation — that machinery dropped as a no-op here). Two deliberate changes, grounded in the Rnote/Saber research: 1. The pan/zoom ScaleGestureRecognizer excludes stylus/invertedStylus via `supportedDevices`. The pen never reaches it, so a stylus stroke can no longer be stolen as a pan on its first frame (the "写字识别成单击" feel bug, caused by stock IV's panEnabled updating a frame after the stroke began). Drawing is owned solely by the canvas Listener; no arena fight, no panEnabled lag. The prior _lastStylus hover hack is removed (superseded). 2. Per-frame scale change is clamped (×0.74..×1.35). Stock IV already damps focal jitter and guards the pan branch, but a single-frame multi-touch glitch could still spike details.scale, popping the zoom bigger/smaller and snapping back (the reported pinch flicker). Clamping swallows the spike; a real (gradual) pinch is unaffected since scale tracks absolutely from gesture start. Cap is far above any real pinch (~1.1-1.2x/frame), so no felt lag. Everything else (scale-about-focal, pan, fling inertia, mouse-wheel zoom) is Flutter's proven logic verbatim. Also add an on-device input diagnostic (bug-report toggle): the existing pen readout already prints kind/pressure/buttons; now it also shows live zoom=now/min/max so the next device test captures (a) whether the side/eraser button arrives as buttons/invertedStylus, and (b) the value any residual pinch flash jumps to. 66/66 tests, analyze clean, linux build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,13 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
String _penDebug = '';
|
||||
bool _showPenDebug = false;
|
||||
|
||||
/// Live zoom-scale diagnostic (only tracked while the overlay is on), so we
|
||||
/// can capture the value the pinch flash jumps to. `_zoomMin/_zoomMax` record
|
||||
/// the extremes seen since the overlay was last enabled.
|
||||
double _zoomNow = 1.0;
|
||||
double _zoomMin = double.infinity;
|
||||
double _zoomMax = 0.0;
|
||||
|
||||
// Tool state.
|
||||
CanvasTool _tool = CanvasTool.pen;
|
||||
Color _color = Colors.black;
|
||||
@@ -119,11 +126,23 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
// Begin listening to the native Windows pen plugin (barrel/eraser/tilt).
|
||||
// No-op on platforms without the plugin (W3).
|
||||
PenInputService.instance.start();
|
||||
_transform.addListener(_onTransformDebug);
|
||||
_initPersistence();
|
||||
_initPenConfig();
|
||||
_open();
|
||||
}
|
||||
|
||||
/// Track the live zoom scale for the diagnostic overlay (no-op when off).
|
||||
void _onTransformDebug() {
|
||||
if (!_showPenDebug) return;
|
||||
final s = _transform.value.getMaxScaleOnAxis();
|
||||
setState(() {
|
||||
_zoomNow = s;
|
||||
if (s < _zoomMin) _zoomMin = s;
|
||||
if (s > _zoomMax) _zoomMax = s;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initPenConfig() async {
|
||||
final controller = await PenConfigController.load();
|
||||
if (!mounted) {
|
||||
@@ -221,6 +240,7 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
scheduler.dispose();
|
||||
}
|
||||
_document?.dispose();
|
||||
_transform.removeListener(_onTransformDebug);
|
||||
_transform.dispose();
|
||||
_penConfig?.dispose();
|
||||
PenInputService.instance.stop();
|
||||
@@ -437,9 +457,10 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 6),
|
||||
child: Text(
|
||||
_penDebug.isEmpty
|
||||
? 'hover / draw with the pen…'
|
||||
: _penDebug,
|
||||
'${_penDebug.isEmpty ? 'hover / draw with the pen…' : _penDebug}'
|
||||
'\nzoom=${_zoomNow.toStringAsFixed(2)} '
|
||||
'min=${_zoomMin.isFinite ? _zoomMin.toStringAsFixed(2) : '-'} '
|
||||
'max=${_zoomMax.toStringAsFixed(2)}',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
@@ -600,8 +621,15 @@ class _PenEditorScreenState extends State<PenEditorScreen> {
|
||||
_ToolButton(
|
||||
icon: Icons.bug_report_outlined,
|
||||
selected: _showPenDebug,
|
||||
tooltip: 'Pen pressure diagnostic',
|
||||
onPressed: () => setState(() => _showPenDebug = !_showPenDebug),
|
||||
tooltip: 'Pen + zoom diagnostic',
|
||||
onPressed: () => setState(() {
|
||||
_showPenDebug = !_showPenDebug;
|
||||
if (_showPenDebug) {
|
||||
_zoomMin = double.infinity;
|
||||
_zoomMax = 0.0;
|
||||
_zoomNow = _transform.value.getMaxScaleOnAxis();
|
||||
}
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user