Buttons (likely fix + ground truth): device diag showed ptr=12577 pen=10056 —
WM_POINTER reaches the observer and GetPointerPenInfo succeeds, so the buttons
were just read from the wrong field. Native now resolves the barrel from BOTH
penFlags(PEN_FLAG_BARREL) AND pointerInfo.pointerFlags(POINTER_FLAG_SECONDBUTTON)
— many pens use the latter. It also emits the full raw set (pointerFlags,
penFlags, penMask, ButtonChangeType, tilt) plus OR-accumulated flags so a single
session reveals exactly which field each button sets.
Comprehensive logging (per user request "用好用的log库 / 我手动开启日志再记录"):
new DiagnosticLogger emits through dart:developer log(name 'badnote.input') —
capturable via `flutter run` / DevTools / `flutter logs` — AND mirrors to a file
(path shown in the overlay) for the packaged GUI build that has no console.
Manually enabled by the toolbar diagnostic toggle; off by default. PEN lines log
on raw-field change; ZOOM lines log every scale frame + rebaselines.
Zoom: scale-only glitch rejection didn't stop the jumping, so add focal/position
glitch rejection — drop a 2-finger frame whose focal jumps >250px (a touch
misread). The full per-frame trace (raw scale, pointerCount, applied change,
focal jump, drops) is now logged so the residual cause is unambiguous.
InputDiagnostics singleton accumulates the stats; the overlay shows summary +
last trace lines + log path + reset. Removed the ad-hoc inline zoom min/max.
Dart: analyze clean, 66/66 tests, linux build green. Native compiles on CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Eraser (regression from the preview I added):
- LAG: the preview did setState on every hover/erase-move (rebuilding the whole
canvas) and recomputed perfect_freehand getStroke per overlapped stroke per
frame. Now the cursor is a ValueNotifier driving the preview layer's repaint
directly (no canvas rebuild), and the highlight is a plain polyline of the
point-runs inside the radius (no getStroke).
- STUCK RED ("一直红着"): the cursor was never cleared. Preview is now
active-erase-only and cleared on pen up/cancel.
- "选中了的笔画也不见得能删掉": radius was strokeWidth*2 (tiny) so a pass removed
~2 points and the stroke survived. Now a decisive fixed 0.02 (page-width
fraction). The highlight traces exactly the point-run that splitStrokeByCircle
removes, so what turns red is what gets deleted.
Zoom: replace the per-frame scale CLAMP with glitch REJECTION — drop a frame
demanding an implausible per-frame scale jump (>1.4x or <0.71x; a real pinch is
≲1.15x/frame). A dropped frame catches up the next frame (absolute tracking), so
no lag, but the Windows multi-touch spike never shows. Pairs with the existing
pointer-count re-baseline.
Native diagnostic: ObservePenMessage now counts WM_POINTER* / PT_PEN / legacy
mouse messages it sees and emits them on the channel; PenInputService exposes
`debugSummary` and the overlay shows `native ptr=… pen=… mouse=… msg=0x…`. This
will tell us on-device whether WM_POINTER ever reaches the observer (→ buttons
recoverable) or Flutter is on a non-pointer path (→ not).
Dart: analyze clean, 66/66 tests, linux build green. Native compiles on CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zoom jumping (device: min 0.5 / max 2.47 while zooming near 1): the per-frame
scale clamp limited single-frame spikes but not multi-frame runs. Root cause is
pointer-count transitions — on Windows touch the two fingers land/lift at
different times and digitizers drop/re-acquire touches, and stock InteractiveViewer
keeps _scaleStart/_referenceFocalPoint from the OLD finger set, so the next frame
jumps. PenInteractiveViewer now re-baselines (and skips the transitional frame)
whenever details.pointerCount changes. The per-frame clamp stays as a secondary
guard.
Buttons (device evidence: btn=1 for tip-down, side-button, AND inverted; kind
never becomes invertedStylus): Flutter does NOT surface the barrel/eraser/inverted
state at all — unreachable from Dart. The only path is the native badnote/pen
plugin, which was SILENT because WM_POINTER is delivered to the Flutter CHILD
view window, not the top-level FlutterWindow where ObservePenMessage was hooked.
Fix: subclass the child HWND (SetWindowSubclass + comctl32) and observe its
WM_POINTER messages, passing every message through unchanged via DefSubclassProc
(observation-only, input behavior preserved). This is what should finally feed
GetPointerPenInfo penFlags + tilt to the channel — to be confirmed on-device with
the diagnostic (btn / kind / tilt readout).
Dart: analyze clean, 66/66 tests, linux build green. Native compiles on Windows CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>