NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev
Platform-agnostic canvas input for Flutter. Unifies mouse, trackpad, and MediaPipe hand-gesture events behind a single CanvasInputController.
Last release 3 months ago
07 Jul 2026
Too new to tell
only 2 release windows
Nearly every release is documented
notes for 4 of 4 stable releases
Nothing withdrawn
no release was ever pulled
3 months old
4 releases · first in 2026
One column per month.
Merge pull request #32 from Ranveer-Singh-Gour/perf/gate-debug-info-o…
Merge pull request #32 from Ranveer-Singh-Gour/perf/gate-debug-info-o…
debugPrint in the web GestureInputSource now only runs in debug builds
(kDebugMode); it previously logged to the console in release builds too.GestureInputSource now keys on dart.library.js_interop instead of the
legacy dart.library.html, so flutter build web --wasm gets the MediaPipe
implementation rather than the native stub.hand_detection LandmarkProvider doc example — the
sketch in landmark_provider.dart called detector.detect(image) with a
raw CameraImage (that method takes decoded image bytes; camera streams
need detectFromCameraImage) and treated Hand.landmarks as already
normalised, when hand_detection returns them in the source image's pixel
space. The example now uses the correct camera-streaming call, normalises
landmarks by the frame's width/height, and aliases hand_detection's
Handedness/HandLandmarkType to avoid colliding with air_pointer's own.GestureInputSource.dispose() (web) posted a 'dispose' message and called
Worker.terminate() in the same tick; since postMessage delivery is
asynchronous, the worker was always hard-terminated before it processed the
message, so landmarker.close() (which releases the WASM/WebGL delegate)
never ran. dispose() now gives the worker a short window to shut itself
down gracefully before terminating it as a backstop.GestureInputSource
parsed worldLandmarks, computed per-hand bounding boxes, and built a full
GestureDebugInfo snapshot on every processed frame regardless of whether
anything was subscribed to debugInfo. That work now only runs when
debugInfo has an active listener, avoiding needless per-frame allocation
for consumers who don't use the debug stream.worldHands/handedness and transferred them across the thread boundary
on every frame regardless of listener state. GestureInputSource now
tells the worker (via a new setDebugEnabled message, sent from
debugInfo's onListen/onCancel) whether to compute that data at all,
and the main thread only dartifies the JS payload fields it actually needs
per frame instead of eagerly converting the whole message.GestureInputSource.workerUrl (web, default 'hand_tracker_worker.js')
— the inference worker script location is now configurable, for apps that
serve the worker from a subdirectory. Must be same-origin.Drag-to-scroll interference — CanvasScrollEvent could fire immediately after releasing a drag when the hand briefly adopted a pointing-like shape duri
CanvasScrollEvent could fire immediatelyscrollConfirmFrames gate (default2, ~67 ms at 30 fps) that requires N consecutive pointing frames beforepinchConfirmFrames guard forscrollConfirmFrames: 1 to restore the old single-frame-baselinehandLandmarkConnections — exported constant listing the 21 skeleton
bone pairs for the MediaPipe hand topology. Iterate over it to draw a
skeleton overlay without hand-coding the connections yourself.
CanvasGestureEvent.confidence (double, default 1.0) — ML
confidence of the gesture classification. Always 1.0 on web (rule-based
heuristic); populated from the backend's score on native when using a
model-based LandmarkProvider such as hand_detection.
HandDetectionFrame.gestureConfidence / secondHandGestureConfidence
(double, default 1.0) — per-gesture confidence fields for native backends
to expose ML scores that flow through to CanvasGestureEvent.confidence.
GestureInputSource confidence thresholds (web) — three new constructor
params minHandDetectionConfidence, minHandPresenceConfidence, and
minTrackingConfidence (each default 0.5, matching MediaPipe defaults).
Tune these to trade sensitivity against false positives without modifying the
JS worker.
HandDetectionFrame.worldLandmarks / secondWorldLandmarks —
optional world-space landmark lists (metric scale, hand-centre origin) for
native backends to expose orientation-invariant landmark data. On web,
populated from MediaPipe's worldLandmarks result automatically.
GestureDebugInfo.worldLandmarks / secondWorldLandmarks — world
landmarks forwarded through the debug snapshot so overlays and classifiers
can consume them.
HandDetectionFrame.boundingBox / secondHandBoundingBox (Rect?)
— axis-aligned bounding box of each detected hand in normalised image
coordinates. Native backends set this from their ML model's output. On web,
computed automatically as the min/max envelope of the 21 image-space
landmarks.
GestureDebugInfo.boundingBox / secondHandBoundingBox (Rect?)
— bounding box forwarded through the debug snapshot for overlay rendering.
StylusInputSource — input source for Apple Pencil, Samsung S-Pen, and
any PointerDeviceKind.stylus / PointerDeviceKind.invertedStylus device.
Maps pen contact to element-drag canvas events (Down/Move/Up, tap, double-tap,
hover, cancel). Filters out mouse/touch/trackpad events so it can be combined
with MouseInputSource or TouchInputSource without duplicates. Exposes an
eraserModeStream that emits true / false on eraser-vs-tip mode changes.
CanvasDownEvent.pressure / CanvasMoveEvent.pressure (double,
default 1.0) — hardware pen pressure (0.0–1.0) carried by stylus down and
move events. Backwards-compatible: non-stylus sources emit 1.0; existing
construction and pattern-matching compiles unchanged.
defaultPointerSource() — factory function that returns the best-fit
CanvasInputSource for the current platform: TouchInputSource on Android /
iOS, MouseInputSource everywhere else (including web). Uses kIsWeb and
defaultTargetPlatform from package:flutter/foundation.dart, so it is safe
to call on web without conditional imports. GestureInputSource is not
included — it requires platform-specific configuration that cannot be inferred
automatically.
TouchInputSource — mobile-native input source for Android and iOS.
Single-finger drag → CanvasScrollEvent (direct-manipulation pan); two-finger
pinch/spread → CanvasScaleEvent; fling → CanvasScrollEvent with non-zero
velocity field for consumer momentum animations. Tap detection uses raw
Listener events rather than GestureDetector so taps are never dropped by
ScaleGestureRecognizer's kPanSlop movement threshold.
CanvasScrollEvent.velocity (Offset, default Offset.zero) — fling
velocity field added to CanvasScrollEvent. Non-zero only on fling events
emitted by TouchInputSource. Backwards-compatible: existing code that
constructs or pattern-matches CanvasScrollEvent compiles unchanged.
CanvasInputController muting (muteWhenActive/activeStream),GestureInputSource.statusStream lifecycle states, andGestureInputSource.setFilterParams().mediaPipeBaseUrl andmodelAssetUrl constructor parameters (Flutter Web only); corrected theGestureInputSource via CanvasScaleEvent.rotation; onlyMouseInputSource omits it (Flutter's ScaleGestureRecognizer limitation).lib/src/filters/ →lib/src/filter/, removed non-existent lib/src/calibration/); addedpubspec.yaml version bumped from 0.2.0 to 0.2.1 (was incorrectlyCHANGELOG.md); added homepage and issue_tracker fields.GestureInputSource (native) — dispose() now wraps _frameSub?.cancel()unawaited(), consistent with the three StreamController.close() callsCanvasScaleEvent.rotation) also rotates the selected piece instead of the`CanvasDoubleTapEvent` — emitted alongside the second CanvasTapEvent when two taps occur within doubleTapWindow (default 300 ms). MouseInputSource det
CanvasDoubleTapEvent — emitted alongside the second CanvasTapEvent when
two taps occur within doubleTapWindow (default 300 ms). MouseInputSource
detects it via a DateTime gap between releases; HandGestureRecognizer via
elapsed time between consecutive dwell taps.CanvasLongPressEvent — emitted after the pointer (or cursor) holds still
for longPressDuration. MouseInputSource uses GestureDetector.onLongPressStart;
HandGestureRecognizer uses a configurable longPressDuration threshold shared
with the dwell timer (shorter threshold always wins).CanvasGestureEvent(gesture) — edge-triggered on each RecognizedGesture
change (non-none) for both the primary and secondary hand. Fires once when a
gesture starts; resets when the hand is lost so the same gesture can fire again
on re-entry.longPressDuration — new constructor param; Duration.zero = disabled (default).doubleTapWindow — new constructor param controlling the inter-tap interval
that qualifies as a double tap (default 300 ms).longPressDurationS / doubleTapWindowS — public getters for the above.Offset(scrollDx, scrollDy) instead of Offset(0, scrollDy))._checkDwell refactored to _checkDwellEvents returning
List<PointerInputEvent> to support multiple simultaneous events (tap +
double-tap).CanvasCancelEvent emitted from Listener.onPointerCancel when the OS
interrupts an active drag (context menu, window switch, etc.).CanvasLongPressEvent via GestureDetector.onLongPressStart. If a drag
was in progress, CanvasCancelEvent is emitted first to close it cleanly.DateTime gap between successive tap releases.longPressDuration and doubleTapWindow forwarded to HandGestureRecognizer
on both web and native variants.maxHands param (default 2) forwarded to the MediaPipe web worker's
numHands option; set to 1 to improve performance in single-hand apps.CanvasGestureEvent emitted for secondary hand gestures in addition to the
primary hand._lastGesture resets to none when tracking is lost, so the same gesture
fires again when the hand re-enters the frame.OneEuroFilter removed from the public barrel export (air_pointer.dart).
It was an internal smoothing detail; import directly from
package:air_pointer/src/filter/one_euro_filter.dart if needed.`PointerInputEvent` sealed hierarchy — CanvasTapEvent, CanvasDownEvent, CanvasMoveEvent, CanvasUpEvent, CanvasCancelEvent, CanvasHoverEvent, CanvasScr
Initial release.
PointerInputEvent sealed hierarchy — CanvasTapEvent, CanvasDownEvent,
CanvasMoveEvent, CanvasUpEvent, CanvasCancelEvent, CanvasHoverEvent,
CanvasScrollEvent (with isTrackpad), CanvasScaleEvent (with rotation),
CanvasScaleEndEvent, CanvasSwipeEvent (with SwipeDirection and velocity).
All events use the Canvas prefix to avoid collisions with Flutter's own pointer events.CanvasInputSource — abstract boundary; all input origins implement this contract.CanvasInputController — merges events from multiple CanvasInputSources into a
single broadcast stream; folds buildSurface wrappers in order.CanvasTapEvent,
one-finger drag → Down/Move/Up, two-finger pinch → CanvasScaleEvent, scroll wheel
and native trackpad pinch (PointerScaleEvent) → CanvasScrollEvent /
CanvasScaleEvent, mouse hover → CanvasHoverEvent.scrollMultiplier — scales scroll deltas before emission.tapSlop — configurable tap-slop threshold (default 10 px).isTrackpad on CanvasScrollEvent — true for macOS/iOS two-finger pan; consumers can
skip ticker-based inertia since the OS already provides momentum.MediaPipe HandLandmarker running in a dedicated web worker (off the main thread).
Zero-copy ImageBitmap transfer. Camera permission / hardware / context errors all
produce typed HandTrackingStatus states.
HandGestureRecognizer — pure-Dart state machine; fully testable without a camera.
CanvasCancelEvent (not CanvasUpEvent) when the hand exits during an active drag.CanvasScaleEvent with rotation delta;
CanvasScaleEndEvent when the second hand leaves.dwellRadius for dwellDuration to emit
CanvasTapEvent. Progress is reported via GestureDebugInfo.dwellProgress (0–1).
Dwell is preserved through the grace window so brief occlusions don't reset progress.CanvasScrollEvent driven by
vertical fingertip movement. Enabled via scrollEnabled: true; scaled by scrollScale.swipeThreshold px/s) in an
open hand emits CanvasSwipeEvent. 60/40 dominance ratio prevents diagonal
false-positives; 400 ms cooldown suppresses repeated firing from one gesture.
Disabled by default (swipeThreshold: 0).OneEuroFilter — adaptive low-pass filter (Casiez et al., CHI 2012) for landmark
coordinates. Exposes velocity for prediction and swipe detection.GestureCalibrator — accumulates open/closed pose samples from
GestureDebugInfo.pinchDistance and computes per-user CalibrationResult.LandmarkProvider — platform interface for native landmark sources (e.g. TFLite on
iOS/Android); web implementation uses MediaPipe via web worker.GestureInputSource.debugInfo stream of GestureDebugInfo
(phase, pinch distance, dwell progress, pointing flag, landmarks, worker latency,
round-trip latency). buildCameraPreview returns a live camera widget.example/ ships two demos driven entirely through CanvasInputController:
Your coding agent can read these notes before it upgrades. Set up the MCP server →