fix: PDF finger ink, chrome UX, OneNote pens, and pen physics
Some checks failed
CI / Windows build (push) Has been cancelled
Some checks failed
CI / Windows build (push) Has been cancelled
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>
This commit is contained in:
@@ -22,6 +22,7 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../engine/brush.dart';
|
||||
import '../engine/pen_physics.dart';
|
||||
import '../engine/stroke_eraser.dart';
|
||||
import '../engine/stroke_geometry.dart' show kDefaultPenThinning;
|
||||
import '../engine/stroke_model.dart';
|
||||
@@ -33,6 +34,7 @@ import '../input/pressure_curve.dart';
|
||||
import '../input/pen_input_service.dart';
|
||||
import '../../diagnostics/pen_event_ring.dart';
|
||||
import '../engine/shape_geometry.dart';
|
||||
import 'dart:math' as math;
|
||||
import '../render/ink_picture_cache.dart';
|
||||
import '../render/live_ink_painter.dart' as render;
|
||||
import '../render/static_ink_painter.dart' as render;
|
||||
@@ -230,6 +232,10 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
/// undo snapshot is recorded once, on the first drag delta — see _extendStroke).
|
||||
bool _selectDragging = false;
|
||||
|
||||
/// Tip-velocity tracker for [tipVelocityWidthScale] (physical ink starvation).
|
||||
Offset? _lastTipNorm;
|
||||
Duration? _lastTipTime;
|
||||
|
||||
/// True when the active stylus reports the eraser signal (barrel button or
|
||||
/// inverted stylus), detected on hover/down.
|
||||
bool _eraserActive = false;
|
||||
@@ -399,7 +405,8 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
if (action == _lastHwAction) return;
|
||||
_lastHwAction = action;
|
||||
if (action == PenButtonAction.undo ||
|
||||
action == PenButtonAction.toggleTool) {
|
||||
action == PenButtonAction.toggleTool ||
|
||||
action == PenButtonAction.select) {
|
||||
widget.onPenButtonAction?.call(action);
|
||||
}
|
||||
}
|
||||
@@ -441,7 +448,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
/// Map a global pointer position into normalized page coords using the
|
||||
/// shared transform (inverse) and this widget's geometry.
|
||||
PenPoint? _toNormalized(Offset globalPosition, double? pressure,
|
||||
{double? tilt}) {
|
||||
{double? tilt, Duration? timeStamp}) {
|
||||
final box = context.findRenderObject() as RenderBox?;
|
||||
if (box == null) return null;
|
||||
final local = box.globalToLocal(globalPosition);
|
||||
@@ -451,7 +458,23 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
|
||||
final nx = scene.dx / widget.pageSize.width;
|
||||
final ny = scene.dy / widget.pageSize.height;
|
||||
return PenPoint(nx, ny, pressure, tilt: tilt);
|
||||
|
||||
double? shaped = pressure;
|
||||
if (shaped != null && timeStamp != null && _lastTipNorm != null &&
|
||||
_lastTipTime != null) {
|
||||
final dt = (timeStamp - _lastTipTime!).inMicroseconds / 1e6;
|
||||
if (dt > 0) {
|
||||
final dx = nx - _lastTipNorm!.dx;
|
||||
final dy = ny - _lastTipNorm!.dy;
|
||||
final speed = math.sqrt(dx * dx + dy * dy) / dt;
|
||||
shaped = (shaped * tipVelocityWidthScale(_currentBrush, speed))
|
||||
.clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
_lastTipNorm = Offset(nx, ny);
|
||||
_lastTipTime = timeStamp;
|
||||
|
||||
return PenPoint(nx, ny, shaped, tilt: tilt);
|
||||
}
|
||||
|
||||
// --- Stroke lifecycle -----------------------------------------------------
|
||||
@@ -464,8 +487,10 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
_shapeStart = null;
|
||||
_selectLast = null;
|
||||
_selectDragging = false;
|
||||
_lastTipNorm = null;
|
||||
_lastTipTime = null;
|
||||
final p = _toNormalized(event.position, _normalizedPressure(event),
|
||||
tilt: _tiltFor(event));
|
||||
tilt: _tiltFor(event), timeStamp: event.timeStamp);
|
||||
|
||||
if (_eraserActive || widget.tool == CanvasTool.eraser) {
|
||||
_eraserCursor.value = p;
|
||||
@@ -502,7 +527,7 @@ class _PenCanvasState extends State<PenCanvas> {
|
||||
|
||||
void _extendStroke(PointerMoveEvent event) {
|
||||
final p = _toNormalized(event.position, _normalizedPressure(event),
|
||||
tilt: _tiltFor(event));
|
||||
tilt: _tiltFor(event), timeStamp: event.timeStamp);
|
||||
if (p == null) return;
|
||||
|
||||
if (_eraserActive || widget.tool == CanvasTool.eraser) {
|
||||
|
||||
Reference in New Issue
Block a user