Wire finger drawing on PDF without breaking pinch; auto-hide page scrubber and fix bounce; share sticky tools with resize and per-page remember; side-button select; separate pen slots with colors; rnote pressure shapes plus tip-velocity width and lower stroke latency.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Hard SDROP avalanches froze lastRaw while zoom still crawled; soft-clamp
and re-anchor instead. Ballpoint is near-solid, pencil uses soft √p without
multiply stacking; PDF ink falls back to nearest page during zoom settle.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire Win32 pressure into Dart, tighten pinch guards, use geometric shape
strokes, expand the ink palette, and replace scratch-link split view with
an on-page sticky that shares the sidecar repo.
Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Device log showed a single-frame scale pop (cur 0.504->0.694, a
+38% jump UP while the pinch was still shrinking).
Root cause: the absolute mapping targetScale = scaleStart *
details.scale is only valid when details.scale is 1.0 at the
moment scaleStart is captured. That holds at gesture start, but
on a mid-gesture re-baseline (a finger blips 2->1->2, routine on
Windows touch) a fresh scaleStart got multiplied by the
recognizer's still-cumulative details.scale, popping the zoom
then snapping back.
Fix: track rawScaleAtBaseline and normalize details.scale against
it so the cumulative reads 1.0 at every baseline. Extracted
absolutePinchScale() pure solver + 5 unit tests covering the
exact re-baseline scenario.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zoom flicker root cause (from the on-device badnote_input_log.txt): the pinch
computed its per-frame scale change as desiredScale / getMaxScaleOnAxis(), i.e.
it fed the LIVE matrix back into its own update. Consecutive frames in the log
show `cur` (the live read) dropping to 0.75-0.89 for a single frame while the
result track stayed smooth, so the code demanded a 1.3-1.4x correction that
popped the zoom bigger/smaller and snapped back. The >1.4 glitch guard missed it
because the spikes sat at 1.31-1.40.
Fix: the scale branch of PenInteractiveViewer now drives the transform
ABSOLUTELY from a gesture-start snapshot (_scaleStart, _referenceFocalPoint) plus
the recognizer's clean, monotonic cumulative details.scale. Each frame is fully
re-derived in closed form (pure scale+translate, no matrix inversion, no live
read-back), so a transient mis-read or interleaved write cannot survive into the
next frame. The per-frame glitch guard now keys on the recognizer's own
scale-ratio (the true finger motion) instead of the corrupted live read. 2-finger
pan still falls out of the same focal-anchor formula.
Pen feel: lower perfect_freehand streamline 0.5 -> 0.32 (new shared constants
kPenStreamline/kPenSmoothing, single-sourced across screen + export so the
parity test still holds). At 0.5 a quick flick lagged so far behind the pen that
short fast strokes collapsed toward their start and rendered as a dot
("写字识别成单击"); 0.32 tracks the real path for a crisper, lower-latency feel.
flutter analyze lib/editor clean; 66/66 tests pass (incl. screen==export parity).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>