// lib/editor/input/input_arbiter.dart // // Pure draw-vs-pan/zoom arbitration for the pen-first canvas (P0 step 4 — // extracted verbatim from `pen_canvas.dart` so the make-or-break gesture rules // are decided by ONE testable place rather than inline in a StatefulWidget). // // The model (clean-room from Saber, proven live): // - A DRAW gesture is exactly ONE active pointer that is a stylus / inverted // stylus / mouse, OR (when the finger-drawing toggle is on) a single finger. // - >= 2 active pointers ALWAYS means pan/zoom (pinch); never draw. // - Palm rejection: a finger never draws unless the user explicitly enabled // finger-drawing — so a resting palm pans (or is ignored) instead of marking. // - A hardware pen button mapped to `pan` suppresses drawing so the shared // InteractiveViewer pans instead. // // These are PURE functions (no widget/IO state) so the whole truth table is // unit-tested; `pen_canvas.dart` owns the live pointer map and delegates the // decisions here. import 'package:flutter/gestures.dart' show PointerDeviceKind; /// Whether [kind] is a pen (tip or flipped eraser end). bool isStylusKind(PointerDeviceKind kind) => kind == PointerDeviceKind.stylus || kind == PointerDeviceKind.invertedStylus; /// Decide whether the gesture currently forming should DRAW. /// /// True iff there is exactly one active pointer, drawing is not suppressed by a /// hardware pan button, and the pointer is a draw device: /// - stylus / inverted stylus → always draws, /// - mouse → always draws (desktop authoring), /// - touch → draws only when [fingerDrawingEnabled] (else it pans / is palm). bool shouldDraw({ required int activePointerCount, required PointerDeviceKind kind, required bool fingerDrawingEnabled, required bool hwPanActive, }) { if (activePointerCount != 1) return false; if (hwPanActive) return false; if (isStylusKind(kind)) return true; if (kind == PointerDeviceKind.mouse) return true; if (kind == PointerDeviceKind.touch) return fingerDrawingEnabled; return false; }