diff --git a/lib/src/view/notifier/scribble_notifier.dart b/lib/src/view/notifier/scribble_notifier.dart index 748a197..34e0bc4 100644 --- a/lib/src/view/notifier/scribble_notifier.dart +++ b/lib/src/view/notifier/scribble_notifier.dart @@ -8,6 +8,7 @@ import 'package:scribble/scribble.dart'; import 'package:scribble/src/view/painting/point_to_offset_x.dart'; import 'package:scribble/src/view/simplification/sketch_simplifier.dart'; import 'package:value_notifier_tools/value_notifier_tools.dart'; +import 'package:vector_math/vector_math_64.dart' show Vector3; /// {@template scribble_notifier_base} /// The base class for a notifier that controls the state of a [Scribble] @@ -225,6 +226,20 @@ class ScribbleNotifier extends ScribbleNotifierBase ); } + /// Sets the view transform matrix for pan/zoom functionality. + /// + /// When set, pointer coordinates are transformed using the inverse of this + /// matrix before being stored, and the sketch is rendered using this + /// transform. This allows Scribble to work seamlessly with external pan/zoom + /// controllers like InteractiveViewer without hit-testing issues. + /// + /// Set to `null` to disable view transformation. + void setViewTransform(Matrix4? transform) { + temporaryValue = value.copyWith( + viewTransform: transform, + ); + } + /// Sets the color of the pen to the given color. void setColor(Color color) { temporaryValue = value.map( @@ -293,6 +308,17 @@ class ScribbleNotifier extends ScribbleNotifierBase @override void onPointerDown(PointerDownEvent event) { if (!value.supportedPointerKinds.contains(event.kind)) return; + + // For singleTouchOnly mode, ignore additional touch pointers but allow + // mouse and stylus to work regardless + if (value.allowedPointersMode == ScribblePointerMode.singleTouchOnly && + value.activePointerIds.isNotEmpty && + event.kind == PointerDeviceKind.touch) { + // Don't capture this pointer - let it pass through to parent widgets + // (e.g., InteractiveViewer for pan/zoom) + return; + } + var s = value; // Are there already pointers on the screen? @@ -308,12 +334,18 @@ class ScribbleNotifier extends ScribbleNotifierBase erasing: (s) => s, ); } else if (value is Drawing) { + // When viewTransform is used, store the intended visual width so lines + // maintain constant thickness regardless of zoom level. + // Otherwise use canvas-coordinate width for backwards compatibility. + final lineWidth = value.viewTransform != null + ? value.selectedWidth + : value.selectedWidth / value.scaleFactor; s = (value as Drawing).copyWith( pointerPosition: _getPointFromEvent(event), activeLine: SketchLine( points: [_getPointFromEvent(event)], color: (value as Drawing).selectedColor, - width: value.selectedWidth / value.scaleFactor, + width: lineWidth, ), ); } @@ -326,6 +358,13 @@ class ScribbleNotifier extends ScribbleNotifierBase @override void onPointerUpdate(PointerMoveEvent event) { if (!value.supportedPointerKinds.contains(event.kind)) return; + + // For singleTouchOnly, ignore updates for pointers we didn't track + if (value.allowedPointersMode == ScribblePointerMode.singleTouchOnly && + !value.activePointerIds.contains(event.pointer)) { + return; + } + if (!value.active) { temporaryValue = value.copyWith( pointerPosition: null, @@ -357,6 +396,13 @@ class ScribbleNotifier extends ScribbleNotifierBase @override void onPointerUp(PointerUpEvent event) { if (!value.supportedPointerKinds.contains(event.kind)) return; + + // For singleTouchOnly, ignore events for pointers we didn't track + if (value.allowedPointersMode == ScribblePointerMode.singleTouchOnly && + !value.activePointerIds.contains(event.pointer)) { + return; + } + final pos = event.kind == PointerDeviceKind.mouse ? value.pointerPosition : null; if (value is Drawing) { @@ -391,6 +437,13 @@ class ScribbleNotifier extends ScribbleNotifierBase @override void onPointerCancel(PointerCancelEvent event) { if (!value.supportedPointerKinds.contains(event.kind)) return; + + // For singleTouchOnly, ignore events for pointers we didn't track + if (value.allowedPointersMode == ScribblePointerMode.singleTouchOnly && + !value.activePointerIds.contains(event.pointer)) { + return; + } + if (value is Drawing) { value = _finishLineForState(_addPoint(event, value)).copyWith( pointerPosition: null, @@ -448,11 +501,21 @@ class ScribbleNotifier extends ScribbleNotifierBase } ScribbleState? _erasePoint(PointerEvent event) { + // Get transformed position for erasing + var position = event.localPosition; + final viewTransform = value.viewTransform; + if (viewTransform != null) { + final inverse = Matrix4.inverted(viewTransform); + final transformed = + inverse.transform3(Vector3(position.dx, position.dy, 0)); + position = Offset(transformed.x, transformed.y); + } + final filteredLines = value.sketch.lines .where( (l) => l.points.every( (p) => - (event.localPosition - p.asOffset).distance > + (position - p.asOffset).distance > l.width + value.selectedWidth, ), ) @@ -470,14 +533,29 @@ class ScribbleNotifier extends ScribbleNotifierBase } /// Converts a pointer event to the [Point] on the canvas. + /// + /// When a viewTransform is set, the coordinates are transformed using the + /// inverse of that matrix to convert screen coordinates to canvas + /// coordinates. Point _getPointFromEvent(PointerEvent event) { + var position = event.localPosition; + + // Apply inverse view transform to get canvas coordinates + final viewTransform = value.viewTransform; + if (viewTransform != null) { + final inverse = Matrix4.inverted(viewTransform); + final transformed = + inverse.transform3(Vector3(position.dx, position.dy, 0)); + position = Offset(transformed.x, transformed.y); + } + final p = event.pressureMin == event.pressureMax ? 0.5 : (event.pressure - event.pressureMin) / (event.pressureMax - event.pressureMin); return Point( - event.localPosition.dx, - event.localPosition.dy, + position.dx, + position.dy, pressure: pressureCurve.transform(p), ); } diff --git a/lib/src/view/painting/scribble_editing_painter.dart b/lib/src/view/painting/scribble_editing_painter.dart index d53748a..a6ee03f 100644 --- a/lib/src/view/painting/scribble_editing_painter.dart +++ b/lib/src/view/painting/scribble_editing_painter.dart @@ -35,6 +35,20 @@ class ScribbleEditingPainter extends CustomPainter with SketchLinePathMixin { @override void paint(Canvas canvas, Size size) { + // Apply view transform if set + final viewTransform = state.viewTransform; + if (viewTransform != null) { + canvas.save(); + canvas.transform(viewTransform.storage); + } + + // When viewTransform is applied, lines are stored with their intended + // visual width. We pass 1/scaleFactor so that after canvas transform + // (which scales by scaleFactor), lines appear at their stored visual width. + // This ensures constant line thickness regardless of zoom level. + final effectiveScaleFactor = + viewTransform != null ? 1.0 / state.scaleFactor : state.scaleFactor; + final paint = Paint()..style = PaintingStyle.fill; final activeLine = state.map( @@ -44,7 +58,7 @@ class ScribbleEditingPainter extends CustomPainter with SketchLinePathMixin { if (activeLine != null) { final path = getPathForLine( activeLine, - scaleFactor: state.scaleFactor, + scaleFactor: effectiveScaleFactor, ); if (path != null) { paint.color = Color(activeLine.color); @@ -66,10 +80,15 @@ class ScribbleEditingPainter extends CustomPainter with SketchLinePathMixin { ..strokeWidth = 1; canvas.drawCircle( state.pointerPosition!.asOffset, - state.selectedWidth / state.scaleFactor, + state.selectedWidth / effectiveScaleFactor, paint, ); } + + // Restore canvas if we applied a transform + if (viewTransform != null) { + canvas.restore(); + } } @override diff --git a/lib/src/view/painting/scribble_painter.dart b/lib/src/view/painting/scribble_painter.dart index 177068f..4b0bff9 100644 --- a/lib/src/view/painting/scribble_painter.dart +++ b/lib/src/view/painting/scribble_painter.dart @@ -9,6 +9,7 @@ class ScribblePainter extends CustomPainter with SketchLinePathMixin { required this.sketch, required this.scaleFactor, required this.simulatePressure, + this.viewTransform, }); /// The [Sketch] to draw. @@ -17,17 +18,33 @@ class ScribblePainter extends CustomPainter with SketchLinePathMixin { /// {@macro view.state.scribble_state.scale_factor} final double scaleFactor; + /// {@macro view.state.scribble_state.view_transform} + final Matrix4? viewTransform; + @override final bool simulatePressure; @override void paint(Canvas canvas, Size size) { + // Apply view transform if set + if (viewTransform != null) { + canvas.save(); + canvas.transform(viewTransform!.storage); + } + + // When viewTransform is applied, lines are stored with their intended + // visual width. We pass 1/scaleFactor so that after canvas transform + // (which scales by scaleFactor), lines appear at their stored visual width. + // This ensures constant line thickness regardless of zoom level. + final effectiveScaleFactor = + viewTransform != null ? 1.0 / scaleFactor : scaleFactor; + final paint = Paint()..style = PaintingStyle.fill; for (var i = 0; i < sketch.lines.length; ++i) { final path = getPathForLine( sketch.lines[i], - scaleFactor: scaleFactor, + scaleFactor: effectiveScaleFactor, ); if (path == null) { continue; @@ -35,12 +52,18 @@ class ScribblePainter extends CustomPainter with SketchLinePathMixin { paint.color = Color(sketch.lines[i].color); canvas.drawPath(path, paint); } + + // Restore canvas if we applied a transform + if (viewTransform != null) { + canvas.restore(); + } } @override bool shouldRepaint(ScribblePainter oldDelegate) { return oldDelegate.sketch != sketch || oldDelegate.simulatePressure != simulatePressure || - oldDelegate.scaleFactor != scaleFactor; + oldDelegate.scaleFactor != scaleFactor || + oldDelegate.viewTransform != viewTransform; } } diff --git a/lib/src/view/scribble.dart b/lib/src/view/scribble.dart index 75d523e..e40e1b0 100644 --- a/lib/src/view/scribble.dart +++ b/lib/src/view/scribble.dart @@ -68,32 +68,52 @@ class Scribble extends StatelessWidget { sketch: state.sketch, scaleFactor: state.scaleFactor, simulatePressure: simulatePressure, + viewTransform: state.viewTransform, ), ), ), ), ); - return !state.active - ? child - : GestureCatcher( - pointerKindsToCatch: state.supportedPointerKinds, - child: MouseRegion( - cursor: drawCurrentTool && - state.supportedPointerKinds - .contains(PointerDeviceKind.mouse) - ? SystemMouseCursors.none - : MouseCursor.defer, - onExit: notifier.onPointerExit, - child: Listener( - onPointerDown: notifier.onPointerDown, - onPointerMove: notifier.onPointerUpdate, - onPointerUp: notifier.onPointerUp, - onPointerHover: notifier.onPointerHover, - onPointerCancel: notifier.onPointerCancel, - child: child, - ), - ), - ); + // For singleTouchOnly mode, don't use GestureCatcher so multi-touch + // events can bubble up to parent widgets (e.g., InteractiveViewer) + final isSingleTouchOnly = + state.allowedPointersMode == ScribblePointerMode.singleTouchOnly; + + final listenerWidget = MouseRegion( + cursor: drawCurrentTool && + state.supportedPointerKinds.contains(PointerDeviceKind.mouse) + ? SystemMouseCursors.none + : MouseCursor.defer, + onExit: notifier.onPointerExit, + child: Listener( + onPointerDown: notifier.onPointerDown, + onPointerMove: notifier.onPointerUpdate, + onPointerUp: notifier.onPointerUp, + onPointerHover: notifier.onPointerHover, + onPointerCancel: notifier.onPointerCancel, + // Use translucent behavior for singleTouchOnly to allow events to + // pass through to parent widgets + behavior: isSingleTouchOnly + ? HitTestBehavior.translucent + : HitTestBehavior.opaque, + child: child, + ), + ); + + if (!state.active) { + return child; + } + + // For singleTouchOnly, skip GestureCatcher to allow multi-touch events + // to reach parent widgets like InteractiveViewer + if (isSingleTouchOnly) { + return listenerWidget; + } + + return GestureCatcher( + pointerKindsToCatch: state.supportedPointerKinds, + child: listenerWidget, + ); }, ); } diff --git a/lib/src/view/state/scribble.state.dart b/lib/src/view/state/scribble.state.dart index c820f71..719cc48 100644 --- a/lib/src/view/state/scribble.state.dart +++ b/lib/src/view/state/scribble.state.dart @@ -1,5 +1,6 @@ import 'dart:ui'; +import 'package:flutter/widgets.dart' show Matrix4; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:scribble/src/domain/model/sketch/sketch.dart'; @@ -25,6 +26,15 @@ enum ScribblePointerMode { /// Allow drawing with both mouse and pen. mouseAndPen, + + /// Allow drawing with a single touch only. + /// + /// When a second touch is detected, the scribble widget will stop capturing + /// events, allowing them to pass through to parent widgets like + /// `InteractiveViewer` for pan/zoom gestures. + /// + /// Mouse and stylus inputs still work for drawing regardless of touch count. + singleTouchOnly, } /// Represents the state of the scribble widget. @@ -70,6 +80,22 @@ sealed class ScribbleState with _$ScribbleState { /// will mean no simplification. /// {@endtemplate} @Default(0) double simplificationTolerance, + + /// {@template view.state.scribble_state.view_transform} + /// The transformation matrix for the view (pan/zoom). + /// + /// When set, coordinates are transformed using the inverse of this matrix + /// before being stored, and the sketch is rendered using this transform. + /// This allows Scribble to work with external pan/zoom controllers like + /// InteractiveViewer without hit-testing issues. + /// + /// Note: This field is not serialized to JSON since Matrix4 has no + /// JSON converter. It defaults to null and is a runtime-only value. + /// {@endtemplate} + // ignore: invalid_annotation_target + @JsonKey(includeFromJson: false, includeToJson: false) + @Default(null) + Matrix4? viewTransform, }) = Drawing; /// The state of the scribble widget when the user is currently erasing. @@ -102,6 +128,12 @@ sealed class ScribbleState with _$ScribbleState { /// Lines will be simplified when they are finished. A value of 0 (default) /// will mean no simplification. @Default(0) double simplificationTolerance, + + /// {@macro view.state.scribble_state.view_transform} + // ignore: invalid_annotation_target + @JsonKey(includeFromJson: false, includeToJson: false) + @Default(null) + Matrix4? viewTransform, }) = Erasing; /// Constructs a [ScribbleState] from a JSON object. @@ -141,6 +173,10 @@ sealed class ScribbleState with _$ScribbleState { PointerDeviceKind.stylus, PointerDeviceKind.invertedStylus, }; + case ScribblePointerMode.singleTouchOnly: + // Accept all device types - multi-touch filtering is done in the + // notifier and widget + return Set.from(PointerDeviceKind.values); } } } diff --git a/lib/src/view/state/scribble.state.freezed.dart b/lib/src/view/state/scribble.state.freezed.dart index ea1774c..5ad2502 100644 --- a/lib/src/view/state/scribble.state.freezed.dart +++ b/lib/src/view/state/scribble.state.freezed.dart @@ -62,6 +62,21 @@ mixin _$ScribbleState { /// will mean no simplification. /// {@endtemplate} double get simplificationTolerance => throw _privateConstructorUsedError; + + /// {@template view.state.scribble_state.view_transform} + /// The transformation matrix for the view (pan/zoom). + /// + /// When set, coordinates are transformed using the inverse of this matrix + /// before being stored, and the sketch is rendered using this transform. + /// This allows Scribble to work with external pan/zoom controllers like + /// InteractiveViewer without hit-testing issues. + /// + /// Note: This field is not serialized to JSON since Matrix4 has no + /// JSON converter. It defaults to null and is a runtime-only value. + /// {@endtemplate} +// ignore: invalid_annotation_target + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? get viewTransform => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ required TResult Function( @@ -73,7 +88,9 @@ mixin _$ScribbleState { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) drawing, required TResult Function( Sketch sketch, @@ -82,7 +99,9 @@ mixin _$ScribbleState { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) erasing, }) => throw _privateConstructorUsedError; @@ -97,7 +116,9 @@ mixin _$ScribbleState { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult? Function( Sketch sketch, @@ -106,7 +127,9 @@ mixin _$ScribbleState { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, }) => throw _privateConstructorUsedError; @@ -121,7 +144,9 @@ mixin _$ScribbleState { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult Function( Sketch sketch, @@ -130,7 +155,9 @@ mixin _$ScribbleState { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, required TResult orElse(), }) => @@ -178,7 +205,9 @@ abstract class $ScribbleStateCopyWith<$Res> { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance}); + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform}); $SketchCopyWith<$Res> get sketch; $PointCopyWith<$Res>? get pointerPosition; @@ -206,6 +235,7 @@ class _$ScribbleStateCopyWithImpl<$Res, $Val extends ScribbleState> Object? selectedWidth = null, Object? scaleFactor = null, Object? simplificationTolerance = null, + Object? viewTransform = freezed, }) { return _then(_value.copyWith( sketch: null == sketch @@ -236,6 +266,10 @@ class _$ScribbleStateCopyWithImpl<$Res, $Val extends ScribbleState> ? _value.simplificationTolerance : simplificationTolerance // ignore: cast_nullable_to_non_nullable as double, + viewTransform: freezed == viewTransform + ? _value.viewTransform + : viewTransform // ignore: cast_nullable_to_non_nullable + as Matrix4?, ) as $Val); } @@ -281,7 +315,9 @@ abstract class _$$DrawingImplCopyWith<$Res> int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance}); + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform}); @override $SketchCopyWith<$Res> get sketch; @@ -312,6 +348,7 @@ class __$$DrawingImplCopyWithImpl<$Res> Object? selectedWidth = null, Object? scaleFactor = null, Object? simplificationTolerance = null, + Object? viewTransform = freezed, }) { return _then(_$DrawingImpl( sketch: null == sketch @@ -350,6 +387,10 @@ class __$$DrawingImplCopyWithImpl<$Res> ? _value.simplificationTolerance : simplificationTolerance // ignore: cast_nullable_to_non_nullable as double, + viewTransform: freezed == viewTransform + ? _value.viewTransform + : viewTransform // ignore: cast_nullable_to_non_nullable + as Matrix4?, )); } @@ -381,6 +422,8 @@ class _$DrawingImpl extends Drawing { this.selectedWidth = 5, this.scaleFactor = 1, this.simplificationTolerance = 0, + @JsonKey(includeFromJson: false, includeToJson: false) + this.viewTransform = null, final String? $type}) : _activePointerIds = activePointerIds, $type = $type ?? 'drawing', @@ -452,12 +495,28 @@ class _$DrawingImpl extends Drawing { @JsonKey() final double simplificationTolerance; + /// {@template view.state.scribble_state.view_transform} + /// The transformation matrix for the view (pan/zoom). + /// + /// When set, coordinates are transformed using the inverse of this matrix + /// before being stored, and the sketch is rendered using this transform. + /// This allows Scribble to work with external pan/zoom controllers like + /// InteractiveViewer without hit-testing issues. + /// + /// Note: This field is not serialized to JSON since Matrix4 has no + /// JSON converter. It defaults to null and is a runtime-only value. + /// {@endtemplate} +// ignore: invalid_annotation_target + @override + @JsonKey(includeFromJson: false, includeToJson: false) + final Matrix4? viewTransform; + @JsonKey(name: 'runtimeType') final String $type; @override String toString() { - return 'ScribbleState.drawing(sketch: $sketch, activeLine: $activeLine, allowedPointersMode: $allowedPointersMode, activePointerIds: $activePointerIds, pointerPosition: $pointerPosition, selectedColor: $selectedColor, selectedWidth: $selectedWidth, scaleFactor: $scaleFactor, simplificationTolerance: $simplificationTolerance)'; + return 'ScribbleState.drawing(sketch: $sketch, activeLine: $activeLine, allowedPointersMode: $allowedPointersMode, activePointerIds: $activePointerIds, pointerPosition: $pointerPosition, selectedColor: $selectedColor, selectedWidth: $selectedWidth, scaleFactor: $scaleFactor, simplificationTolerance: $simplificationTolerance, viewTransform: $viewTransform)'; } @override @@ -482,7 +541,9 @@ class _$DrawingImpl extends Drawing { other.scaleFactor == scaleFactor) && (identical( other.simplificationTolerance, simplificationTolerance) || - other.simplificationTolerance == simplificationTolerance)); + other.simplificationTolerance == simplificationTolerance) && + (identical(other.viewTransform, viewTransform) || + other.viewTransform == viewTransform)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -497,7 +558,8 @@ class _$DrawingImpl extends Drawing { selectedColor, selectedWidth, scaleFactor, - simplificationTolerance); + simplificationTolerance, + viewTransform); /// Create a copy of ScribbleState /// with the given fields replaced by the non-null parameter values. @@ -519,7 +581,9 @@ class _$DrawingImpl extends Drawing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) drawing, required TResult Function( Sketch sketch, @@ -528,7 +592,9 @@ class _$DrawingImpl extends Drawing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) erasing, }) { return drawing( @@ -540,7 +606,8 @@ class _$DrawingImpl extends Drawing { selectedColor, selectedWidth, scaleFactor, - simplificationTolerance); + simplificationTolerance, + viewTransform); } @override @@ -555,7 +622,9 @@ class _$DrawingImpl extends Drawing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult? Function( Sketch sketch, @@ -564,7 +633,9 @@ class _$DrawingImpl extends Drawing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, }) { return drawing?.call( @@ -576,7 +647,8 @@ class _$DrawingImpl extends Drawing { selectedColor, selectedWidth, scaleFactor, - simplificationTolerance); + simplificationTolerance, + viewTransform); } @override @@ -591,7 +663,9 @@ class _$DrawingImpl extends Drawing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult Function( Sketch sketch, @@ -600,7 +674,9 @@ class _$DrawingImpl extends Drawing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, required TResult orElse(), }) { @@ -614,7 +690,8 @@ class _$DrawingImpl extends Drawing { selectedColor, selectedWidth, scaleFactor, - simplificationTolerance); + simplificationTolerance, + viewTransform); } return orElse(); } @@ -668,7 +745,9 @@ abstract class Drawing extends ScribbleState { final int selectedColor, final double selectedWidth, final double scaleFactor, - final double simplificationTolerance}) = _$DrawingImpl; + final double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + final Matrix4? viewTransform}) = _$DrawingImpl; const Drawing._() : super._(); factory Drawing.fromJson(Map json) = _$DrawingImpl.fromJson; @@ -719,6 +798,22 @@ abstract class Drawing extends ScribbleState { @override double get simplificationTolerance; + /// {@template view.state.scribble_state.view_transform} + /// The transformation matrix for the view (pan/zoom). + /// + /// When set, coordinates are transformed using the inverse of this matrix + /// before being stored, and the sketch is rendered using this transform. + /// This allows Scribble to work with external pan/zoom controllers like + /// InteractiveViewer without hit-testing issues. + /// + /// Note: This field is not serialized to JSON since Matrix4 has no + /// JSON converter. It defaults to null and is a runtime-only value. + /// {@endtemplate} +// ignore: invalid_annotation_target + @override + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? get viewTransform; + /// Create a copy of ScribbleState /// with the given fields replaced by the non-null parameter values. @override @@ -742,7 +837,9 @@ abstract class _$$ErasingImplCopyWith<$Res> Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance}); + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform}); @override $SketchCopyWith<$Res> get sketch; @@ -770,6 +867,7 @@ class __$$ErasingImplCopyWithImpl<$Res> Object? selectedWidth = null, Object? scaleFactor = null, Object? simplificationTolerance = null, + Object? viewTransform = freezed, }) { return _then(_$ErasingImpl( sketch: null == sketch @@ -800,6 +898,10 @@ class __$$ErasingImplCopyWithImpl<$Res> ? _value.simplificationTolerance : simplificationTolerance // ignore: cast_nullable_to_non_nullable as double, + viewTransform: freezed == viewTransform + ? _value.viewTransform + : viewTransform // ignore: cast_nullable_to_non_nullable + as Matrix4?, )); } } @@ -815,6 +917,8 @@ class _$ErasingImpl extends Erasing { this.selectedWidth = 5, this.scaleFactor = 1, this.simplificationTolerance = 0, + @JsonKey(includeFromJson: false, includeToJson: false) + this.viewTransform = null, final String? $type}) : _activePointerIds = activePointerIds, $type = $type ?? 'erasing', @@ -873,12 +977,18 @@ class _$ErasingImpl extends Erasing { @JsonKey() final double simplificationTolerance; + /// {@macro view.state.scribble_state.view_transform} +// ignore: invalid_annotation_target + @override + @JsonKey(includeFromJson: false, includeToJson: false) + final Matrix4? viewTransform; + @JsonKey(name: 'runtimeType') final String $type; @override String toString() { - return 'ScribbleState.erasing(sketch: $sketch, allowedPointersMode: $allowedPointersMode, activePointerIds: $activePointerIds, pointerPosition: $pointerPosition, selectedWidth: $selectedWidth, scaleFactor: $scaleFactor, simplificationTolerance: $simplificationTolerance)'; + return 'ScribbleState.erasing(sketch: $sketch, allowedPointersMode: $allowedPointersMode, activePointerIds: $activePointerIds, pointerPosition: $pointerPosition, selectedWidth: $selectedWidth, scaleFactor: $scaleFactor, simplificationTolerance: $simplificationTolerance, viewTransform: $viewTransform)'; } @override @@ -899,7 +1009,9 @@ class _$ErasingImpl extends Erasing { other.scaleFactor == scaleFactor) && (identical( other.simplificationTolerance, simplificationTolerance) || - other.simplificationTolerance == simplificationTolerance)); + other.simplificationTolerance == simplificationTolerance) && + (identical(other.viewTransform, viewTransform) || + other.viewTransform == viewTransform)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -912,7 +1024,8 @@ class _$ErasingImpl extends Erasing { pointerPosition, selectedWidth, scaleFactor, - simplificationTolerance); + simplificationTolerance, + viewTransform); /// Create a copy of ScribbleState /// with the given fields replaced by the non-null parameter values. @@ -934,7 +1047,9 @@ class _$ErasingImpl extends Erasing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) drawing, required TResult Function( Sketch sketch, @@ -943,11 +1058,20 @@ class _$ErasingImpl extends Erasing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance) + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform) erasing, }) { - return erasing(sketch, allowedPointersMode, activePointerIds, - pointerPosition, selectedWidth, scaleFactor, simplificationTolerance); + return erasing( + sketch, + allowedPointersMode, + activePointerIds, + pointerPosition, + selectedWidth, + scaleFactor, + simplificationTolerance, + viewTransform); } @override @@ -962,7 +1086,9 @@ class _$ErasingImpl extends Erasing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult? Function( Sketch sketch, @@ -971,11 +1097,20 @@ class _$ErasingImpl extends Erasing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, }) { - return erasing?.call(sketch, allowedPointersMode, activePointerIds, - pointerPosition, selectedWidth, scaleFactor, simplificationTolerance); + return erasing?.call( + sketch, + allowedPointersMode, + activePointerIds, + pointerPosition, + selectedWidth, + scaleFactor, + simplificationTolerance, + viewTransform); } @override @@ -990,7 +1125,9 @@ class _$ErasingImpl extends Erasing { int selectedColor, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? drawing, TResult Function( Sketch sketch, @@ -999,13 +1136,22 @@ class _$ErasingImpl extends Erasing { Point? pointerPosition, double selectedWidth, double scaleFactor, - double simplificationTolerance)? + double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? viewTransform)? erasing, required TResult orElse(), }) { if (erasing != null) { - return erasing(sketch, allowedPointersMode, activePointerIds, - pointerPosition, selectedWidth, scaleFactor, simplificationTolerance); + return erasing( + sketch, + allowedPointersMode, + activePointerIds, + pointerPosition, + selectedWidth, + scaleFactor, + simplificationTolerance, + viewTransform); } return orElse(); } @@ -1057,7 +1203,9 @@ abstract class Erasing extends ScribbleState { final Point? pointerPosition, final double selectedWidth, final double scaleFactor, - final double simplificationTolerance}) = _$ErasingImpl; + final double simplificationTolerance, + @JsonKey(includeFromJson: false, includeToJson: false) + final Matrix4? viewTransform}) = _$ErasingImpl; const Erasing._() : super._(); factory Erasing.fromJson(Map json) = _$ErasingImpl.fromJson; @@ -1098,6 +1246,12 @@ abstract class Erasing extends ScribbleState { @override double get simplificationTolerance; + /// {@macro view.state.scribble_state.view_transform} +// ignore: invalid_annotation_target + @override + @JsonKey(includeFromJson: false, includeToJson: false) + Matrix4? get viewTransform; + /// Create a copy of ScribbleState /// with the given fields replaced by the non-null parameter values. @override diff --git a/lib/src/view/state/scribble.state.g.dart b/lib/src/view/state/scribble.state.g.dart index a76c097..2933433 100644 --- a/lib/src/view/state/scribble.state.g.dart +++ b/lib/src/view/state/scribble.state.g.dart @@ -50,6 +50,7 @@ const _$ScribblePointerModeEnumMap = { ScribblePointerMode.mouseOnly: 'mouseOnly', ScribblePointerMode.penOnly: 'penOnly', ScribblePointerMode.mouseAndPen: 'mouseAndPen', + ScribblePointerMode.singleTouchOnly: 'singleTouchOnly', }; _$ErasingImpl _$$ErasingImplFromJson(Map json) => diff --git a/pubspec.yaml b/pubspec.yaml index 08ef6a8..d235cc0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: perfect_freehand: ^2.3.2 simpli: ^0.1.1 value_notifier_tools: ^0.1.2 + vector_math: ^2.1.0 dev_dependencies: build_runner: ^2.4.9