Skip to content

Conversation

@escamoteur
Copy link

Summary

This PR adds viewTransform support to Scribble, enabling seamless integration with external pan/zoom controllers like InteractiveViewer. This solves hit-testing issues that occur when using Transform widgets for zoom.

Problem

When wrapping Scribble in an InteractiveViewer (or similar), the Transform widget affects hit testing. When zoomed out, touches at the edges of the viewport map to coordinates outside Scribble's bounds, causing strokes to fail.

Solution

Instead of transforming the widget, transform coordinates inside Scribble:

  1. Scribble stays viewport-sized - Always receives all touches
  2. Coordinates transformed - Pointer positions transformed using inverse matrix before storing
  3. Rendering transformed - Canvas applies viewTransform when painting
  4. Constant line thickness - Lines maintain visual width regardless of zoom level

Changes

ScribbleState

  • Added viewTransform: Matrix4? field to both Drawing and Erasing states
  • Field is excluded from JSON serialization (runtime-only value)

ScribbleNotifier

  • Added setViewTransform(Matrix4? transform) method
  • _getPointFromEvent() transforms coordinates using inverse viewTransform
  • _erasePoint() also transforms coordinates for consistent erasing
  • When viewTransform is set, lines are stored with visual width to maintain constant thickness

Painters

  • Apply viewTransform to canvas before rendering
  • Use 1/scaleFactor for line width when viewTransform is set
  • Lines maintain constant visual thickness at any zoom level

Dependencies

  • Added vector_math to dependencies for Matrix4/Vector3 operations

Usage

// Use with a custom pan/zoom controller that only handles two-finger gestures:
TwoFingerInteractiveViewer(
  transformationController: _transformController,
  applyTransformToChild: false, // Don't wrap in Transform
  child: Scribble(notifier: _scribbleNotifier),
);

// Sync transform to Scribble:
_transformController.addListener(() {
  final transform = _transformController.value;
  _scribbleNotifier.setViewTransform(transform);
  _scribbleNotifier.setScaleFactor(transform.getMaxScaleOnAxis());
});

Backwards Compatibility

  • When viewTransform is null, behavior is unchanged
  • Existing sketches and usage patterns continue to work

This feature allows Scribble to work seamlessly with external pan/zoom
controllers (like InteractiveViewer) by transforming coordinates internally
rather than relying on widget-level transforms. This solves hit-testing
issues that occur when using Transform widgets for zoom.

## Changes

### ScribbleState
- Added `viewTransform: Matrix4?` field to both Drawing and Erasing states
- Field is excluded from JSON serialization (runtime-only value)

### ScribbleNotifier
- Added `setViewTransform(Matrix4? transform)` method
- `_getPointFromEvent()` now transforms coordinates using inverse viewTransform
- `_erasePoint()` also transforms coordinates for consistent erasing
- When viewTransform is set, lines are stored with visual width (not divided
  by scaleFactor) to maintain constant thickness regardless of zoom

### Painters (ScribblePainter, ScribbleEditingPainter)
- Apply viewTransform to canvas before rendering
- Use `1/scaleFactor` for line width when viewTransform is set, so after
  canvas transform, lines appear at their intended visual width
- Lines maintain constant visual thickness at any zoom level

### Dependencies
- Added vector_math to dependencies for Matrix4/Vector3 operations

## Usage

```dart
// In your pan/zoom controller callback:
_transformController.addListener(() {
  final transform = _transformController.value;
  _scribbleNotifier.setViewTransform(transform);
  _scribbleNotifier.setScaleFactor(transform.getMaxScaleOnAxis());
});
```

## How it works

1. Scribble stays viewport-sized (always receives all touches)
2. Pointer coordinates are transformed using inverse matrix before storing
3. Canvas applies viewTransform when painting
4. Lines maintain constant visual thickness regardless of zoom level
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant