Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
e3343e8
Initial plan
Copilot Feb 5, 2026
301a925
Add gesture surface controls for MAUI (port of PR #79)
Copilot Feb 5, 2026
156564d
Fix code review and security issues
Copilot Feb 5, 2026
7aa5a70
Delete copilot-setup.yml
mattleibow Feb 19, 2026
0e56bc5
Delete copilot-instructions.md
mattleibow Feb 19, 2026
cd25f6a
Delete PR79-MAUI-PORT-TRACKING.md
mattleibow Feb 19, 2026
2eccdb4
Refactor gesture view with testable engine and sample demo
Copilot Feb 19, 2026
b8e6328
Fix pinch and fling gesture tests
Copilot Feb 19, 2026
da09be3
Address review comments: use BCL timer, add SKGLView support, simplifโ€ฆ
Copilot Feb 20, 2026
ce0911e
Remove lock by using SynchronizationContext for UI-thread-only operation
Copilot Feb 20, 2026
00df4a7
Fix GesturePage to render stickers with grid background
Copilot Feb 20, 2026
ef2d30d
Add smooth fling animation with deceleration
Copilot Feb 20, 2026
69aec18
Fix gesture engine bugs found by 3-model code review
mattleibow Feb 20, 2026
06ea747
Fix display density scaling for touch coordinates and canvas size
mattleibow Feb 20, 2026
d198c00
Add double-tap zoom at tapped point in gesture demo
mattleibow Feb 20, 2026
f0a3915
Draw grid inside canvas transform so it zooms with content
mattleibow Feb 20, 2026
1fe9f69
Fix grid checker pattern and rotation pivot center
mattleibow Feb 20, 2026
8615896
Pivot pinch/rotate/zoom around touch center point
mattleibow Feb 20, 2026
2711ba8
Transform pan/fling deltas from screen to content space
mattleibow Feb 20, 2026
b72576a
Track pinch/rotate center movement to pan content with fingers
mattleibow Feb 20, 2026
337fec1
Fix 2x pan during pinch by removing duplicate center delta
mattleibow Feb 20, 2026
a26f053
Add settings page with gesture toggles and threshold sliders
mattleibow Feb 20, 2026
072d9fb
Fix HitTest matrix order and gate pinch pan on _enablePan
mattleibow Feb 20, 2026
b54eefa
Fix HitTest matrix: PreConcat in same order as canvas calls
mattleibow Feb 20, 2026
60e7ccf
Add comprehensive gesture engine test coverage (70 tests)
mattleibow Feb 20, 2026
0857939
Refactor: SK prefix and one-type-per-file for gesture types
mattleibow Feb 20, 2026
1d55a0e
Apply dotnet format to gesture types
mattleibow Feb 20, 2026
4251c9c
Move fling animation into SKGestureEngine
mattleibow Feb 20, 2026
119bcdc
Fix hover without prior touch + add mouse wheel scroll zoom
mattleibow Feb 20, 2026
9ced4ac
Wire HoverDetected to sample for testing on macOS
mattleibow Feb 20, 2026
1b6ea28
Add touch event inspector to Playground page
mattleibow Feb 20, 2026
537e3cc
Revert "Add touch event inspector to Playground page"
mattleibow Feb 20, 2026
56f1efd
Fix 10 issues from 5-model code review
mattleibow Feb 20, 2026
9e731cd
Merge remote-tracking branch 'origin/main' into copilot/copy-skia-to-โ€ฆ
mattleibow Feb 24, 2026
2aa7a69
Fix 2x pan speed by moving event subscription to Loaded/Unloaded
mattleibow Feb 24, 2026
adf34c6
Add fling and threshold settings to demo settings page
mattleibow Feb 24, 2026
ec9dd5f
Add 3-layer gesture architecture with SKGestureTracker
mattleibow Feb 25, 2026
73b3bf5
Fix pan/drag interaction and sticker movement
mattleibow Feb 25, 2026
ece50d3
Remove SKGestureSurfaceView, use SKGestureTracker directly in sample
Copilot Feb 28, 2026
293f3db
Merge branch 'main' into copilot/copy-skia-to-maui
mattleibow Feb 28, 2026
ffb2e93
Add Blazor gesture sample, remove SKGestureTrackerExtensions from libโ€ฆ
Copilot Feb 28, 2026
ecc6225
Fix pan speed and rotation/zoom pivot point calculations
Copilot Feb 28, 2026
5874832
Revert transform math, fix Blazor DPI scaling
Copilot Feb 28, 2026
001540c
Fix coordinate space mismatch and fling after rotate/drag
mattleibow Feb 28, 2026
c07eac3
Remove DisplayScale from SKGestureTracker
mattleibow Feb 28, 2026
1865242
Make SKGestureState and SKGestureStateEventArgs internal
mattleibow Feb 28, 2026
7f81fd3
Make GestureStarted/GestureEnded public on engine and tracker
mattleibow Feb 28, 2026
dc01d30
Remove dead SKGestureStateEventArgs class
mattleibow Feb 28, 2026
74d2f42
Nest SKTouchState as private record in SKGestureEngine
mattleibow Feb 28, 2026
facd2a2
Nest SKPinchState as private record in SKGestureEngine
mattleibow Feb 28, 2026
f290357
Convert FlingEvent to positional record struct
mattleibow Feb 28, 2026
9c841a5
Extract engine config into SKGestureEngineOptions
mattleibow Feb 28, 2026
77d6b2c
Extract tracker config into SKGestureTrackerOptions
mattleibow Feb 28, 2026
e0ac6aa
Add velocity to SKPanEventArgs
mattleibow Mar 1, 2026
c23fb86
Rename Center/PreviousCenter to FocalPoint/PreviousFocalPoint
mattleibow Mar 1, 2026
2e37daa
Rename EventArgs to SK*GestureEventArgs pattern
mattleibow Mar 1, 2026
bfbb428
Add missing feature toggles for all gesture types
mattleibow Mar 1, 2026
f7ae824
Add Gestures to Blazor home page
mattleibow Mar 1, 2026
af4d3c0
Add config/settings UI to Blazor gesture demo
mattleibow Mar 1, 2026
74f0039
Add comprehensive tests for feature toggles, options, pan velocity
mattleibow Mar 1, 2026
9215298
Rename SKGestureEngine โ†’ SKGestureDetector, fix netstandard2.0 build
mattleibow Mar 1, 2026
eb5dafd
Add conceptual docs for gesture system
mattleibow Mar 1, 2026
8181a0f
Create SKGestureEventArgs base class for all gesture event args
mattleibow Mar 1, 2026
d79267a
Create SKGestureLifecycleEventArgs for GestureStarted/Ended events
mattleibow Mar 1, 2026
920fdd5
Create SKLongPressGestureEventArgs with Location and Duration
mattleibow Mar 1, 2026
2a2312f
Fix PanDetected firing when IsPanEnabled=false
mattleibow Mar 1, 2026
6290b4e
Rename Scale to ScaleDelta on SKPinchGestureEventArgs
mattleibow Mar 1, 2026
de4f475
Add options validation to SKGestureDetectorOptions and SKGestureTrackโ€ฆ
mattleibow Mar 1, 2026
5431b2e
Thread safety: capture SyncContext to local before null-check
mattleibow Mar 1, 2026
3b6fe17
Rename Flinging event to FlingUpdated in SKGestureTracker
mattleibow Mar 1, 2026
414d2fc
Move feature toggles (Is*Enabled) to SKGestureTrackerOptions
mattleibow Mar 1, 2026
ffc92c5
Add SetTransform, SetScale, SetRotation, SetOffset methods
mattleibow Mar 1, 2026
4e504c9
Remove duplicate forwarding properties from SKGestureTracker
mattleibow Mar 1, 2026
da97821
Store isMouse in TouchState for reliable mouse detection
mattleibow Mar 1, 2026
57c52ee
Update gesture docs for API changes
mattleibow Mar 1, 2026
aced369
Add comprehensive XML documentation to gesture API
mattleibow Mar 1, 2026
be6b522
Enable GenerateDocumentationFile for API docs
mattleibow Mar 1, 2026
97e091d
Refactor tracker to use (0,0) matrix origin
mattleibow Mar 1, 2026
36b07ce
Fix GestureStarted multi-fire, MinScale validation, and pinch radius
mattleibow Mar 2, 2026
ddda281
Add 28 new tests: options validation, EventArgs verification, strongeโ€ฆ
mattleibow Mar 2, 2026
552b2d5
Merge branch 'main' into copilot/copy-skia-to-maui
mattleibow Mar 2, 2026
f2a1a13
Remove SKGestureEventArgs base class; inline Handled into types that โ€ฆ
mattleibow Mar 2, 2026
57cddfc
Seal SKGestureDetector and SKGestureTracker
mattleibow Mar 2, 2026
bc2daee
Add tests: double-dispose, touch ID reuse, SetScale boundaries, Transโ€ฆ
mattleibow Mar 2, 2026
41e5732
Fix tracker disposed on settings navigation; add all missing settings
mattleibow Mar 2, 2026
d0e5bb2
Dispose and recreate tracker on navigation to avoid resource leaks
mattleibow Mar 2, 2026
c5d1ffb
Use OnHandlerChanged for tracker lifecycle instead of OnAppearing/OnDโ€ฆ
mattleibow Mar 2, 2026
20c4bb5
Fix gesture recognition issues: ProcessTouchCancel transitions, ZoomTโ€ฆ
mattleibow Mar 2, 2026
3e1ccb6
test: add failing tests proving gesture recognition bugs
mattleibow Mar 2, 2026
473d7f0
fix: correct gesture recognition bugs in SKGestureTracker and SKGestuโ€ฆ
mattleibow Mar 2, 2026
0fff5df
fix: correct AdjustOffsetForPivot math, tapCount desync, fling TimePrโ€ฆ
mattleibow Mar 3, 2026
4e0b5f9
refactor: move gesture classes to root SkiaSharp.Extended namespace
mattleibow Mar 3, 2026
c94d195
docs: restructure gestures into quick-start and sub-pages, extract CSS
mattleibow Mar 3, 2026
be135a4
fix: add missing using SkiaSharp.Extended in GesturePage after namespโ€ฆ
mattleibow Mar 3, 2026
8100549
refactor: make event args consistent across gesture system
mattleibow Mar 3, 2026
56e22e7
refactor: split large gesture test files into smaller area-specific fโ€ฆ
mattleibow Mar 3, 2026
ee295f5
refactor: rename PrevLocation to PreviousLocation for consistency
mattleibow Mar 3, 2026
11c2788
Remove regions from test files
mattleibow Mar 3, 2026
0f0298c
Fix review findings: dead code, event gating, allocations, time, docsโ€ฆ
mattleibow Mar 3, 2026
dbe5229
Fix critical review findings: NaN Clamp, scroll zoom negative delta, โ€ฆ
mattleibow Mar 3, 2026
59c8828
Merge remote-tracking branch 'origin/main' into copilot/copy-skia-to-โ€ฆ
mattleibow Mar 4, 2026
0002754
Better skill
mattleibow Mar 4, 2026
17099a5
Merge branch 'main' into copilot/copy-skia-to-maui
mattleibow Mar 4, 2026
7223663
Fix netstandard2.0 build and add edge case tests
mattleibow Mar 4, 2026
ab159a7
Merge remote-tracking branch 'origin/main' into copilot/copy-skia-to-โ€ฆ
mattleibow Mar 5, 2026
fbe6b20
fix: PinchDetected event leak, false DoubleTapDetected, MinScale/MaxSโ€ฆ
mattleibow Mar 5, 2026
b45d663
fix: lock pinch zoom pivot when pan is disabled
mattleibow Mar 6, 2026
1e6280a
refactor: centralize gesture pivot locking in GetEffectiveGesturePivot
mattleibow Mar 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions docs/docs/gesture-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Configuration & Customization

This page covers how to configure gesture thresholds, enable/disable features, read transform state, and programmatically control `SKGestureTracker`. For the quick-start guide, see [Gestures](gestures.md).

## Options

Configure thresholds and behavior through `SKGestureTrackerOptions`:

```csharp
var options = new SKGestureTrackerOptions
{
// Detection thresholds (inherited from SKGestureDetectorOptions)
TouchSlop = 8f, // Pixels to move before pan starts (default: 8)
DoubleTapSlop = 40f, // Max distance between double-tap taps (default: 40)
FlingThreshold = 200f, // Min velocity (px/s) to trigger fling (default: 200)
LongPressDuration = 500, // Milliseconds to hold for long press (default: 500)

// Scale limits
MinScale = 0.1f, // Minimum zoom level (default: 0.1)
MaxScale = 10f, // Maximum zoom level (default: 10)

// Double-tap zoom
DoubleTapZoomFactor = 2f, // Scale multiplier on double tap (default: 2)
ZoomAnimationDuration = 250, // Animation duration in ms (default: 250)
ZoomAnimationInterval = 16, // Frame interval for zoom animation in ms (~60fps) (default: 16)

// Scroll zoom
ScrollZoomFactor = 0.1f, // Zoom per scroll unit (default: 0.1)

// Fling animation
FlingFriction = 0.08f, // Velocity decay per frame (default: 0.08)
FlingMinVelocity = 5f, // Stop threshold in px/s (default: 5)
FlingFrameInterval = 16, // Frame interval in ms (~60fps) (default: 16)
};

var tracker = new SKGestureTracker(options);
```

You can also modify options at runtime:

```csharp
tracker.Options.MinScale = 0.5f;
tracker.Options.MaxScale = 20f;
tracker.Options.DoubleTapZoomFactor = 3f;
```

## Feature Toggles

Enable or disable individual gesture types at runtime. Feature toggles can be set at construction time or modified later:

```csharp
// Configure at construction time via options
var options = new SKGestureTrackerOptions
{
IsTapEnabled = true,
IsPanEnabled = true,
IsPinchEnabled = false,
IsRotateEnabled = false,
};
var tracker = new SKGestureTracker(options);

// Or toggle at runtime
tracker.IsTapEnabled = false;
tracker.IsDoubleTapEnabled = false;
tracker.IsLongPressEnabled = false;
tracker.IsPanEnabled = false;
tracker.IsPinchEnabled = false;
tracker.IsRotateEnabled = false;
tracker.IsFlingEnabled = false;
tracker.IsDoubleTapZoomEnabled = false;
tracker.IsScrollZoomEnabled = false;
tracker.IsHoverEnabled = false;
```

When a gesture is disabled, the tracker suppresses its events. The underlying detector still recognizes the gesture, so you can re-enable it at runtime without losing state.

## Reading Transform State

```csharp
float scale = tracker.Scale; // Current zoom level
float rotation = tracker.Rotation; // Current rotation in degrees
SKPoint offset = tracker.Offset; // Current pan offset
SKMatrix matrix = tracker.Matrix; // Combined transform matrix
```

## Programmatic Transform Control

You can set the transform directly without any touch input:

```csharp
// Reset everything back to identity
tracker.Reset();

// Set all values at once
tracker.SetTransform(scale: 2f, rotation: 45f, offset: new SKPoint(100, 50));

// Set individual components
tracker.SetScale(1.5f);
tracker.SetScale(2f, pivot: new SKPoint(400, 300)); // Scale around a specific point
tracker.SetRotation(0f);
tracker.SetRotation(45f, pivot: new SKPoint(400, 300)); // Rotate around a specific point
tracker.SetOffset(SKPoint.Empty);
```

### Animated Zoom

Use `ZoomTo` to animate a zoom by a given factor with a smooth ease-out curve:

```csharp
// Zoom in by 3x at the center of the view
tracker.ZoomTo(factor: 3f, focalPoint: new SKPoint(400, 300));

// Check animation state
bool animating = tracker.IsZoomAnimating;
```

The animation duration and frame interval are controlled by `ZoomAnimationDuration` and `ZoomAnimationInterval` in the options.

## See Also

- [Gestures โ€” Quick Start](gestures.md)
- [Gesture Events](gesture-events.md)
- [API Reference โ€” SKGestureTrackerOptions](xref:SkiaSharp.Extended.SKGestureTrackerOptions)
175 changes: 175 additions & 0 deletions docs/docs/gesture-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Gesture Events

This page covers all gesture events raised by `SKGestureTracker`, with code examples for each. For the quick-start guide and architecture overview, see [Gestures](gestures.md).

## Tap, Double Tap, Long Press

Single finger gestures detected after the finger lifts (or after a timeout for long press).

```csharp
tracker.TapDetected += (s, e) =>
{
// e.Location โ€” where the tap occurred
// e.TapCount โ€” always 1 for single tap
};

tracker.DoubleTapDetected += (s, e) =>
{
// Two taps within DoubleTapSlop distance and timing
// By default, also triggers a zoom animation (see Double Tap Zoom below)
// Set e.Handled = true to prevent the zoom
};

tracker.LongPressDetected += (s, e) =>
{
// Finger held down without moving for LongPressDuration (default 500ms)
// e.Location โ€” where the press occurred
// e.Duration โ€” how long the finger was held
};
```

## Pan

Single finger drag. The tracker automatically updates its internal offset.

```csharp
tracker.PanDetected += (s, e) =>
{
// e.Location โ€” current position
// e.PreviousLocation โ€” previous position
// e.Delta โ€” movement since last event
// e.Velocity โ€” current velocity in pixels/second
};
```

## Pinch (Scale)

Two finger pinch gesture. The tracker automatically updates its internal scale, clamped to `MinScale`/`MaxScale`.

```csharp
tracker.PinchDetected += (s, e) =>
{
// e.ScaleDelta โ€” relative scale change (>1 = spread, <1 = pinch)
// e.FocalPoint โ€” midpoint between the two fingers
// e.PreviousFocalPoint โ€” previous midpoint
};
```

## Rotate

Two finger rotation. The tracker automatically updates its internal rotation.

```csharp
tracker.RotateDetected += (s, e) =>
{
// e.RotationDelta โ€” change in degrees
// e.FocalPoint โ€” center of rotation
};
```

## Fling

Momentum-based animation after a fast pan. The tracker runs a fling animation that decays over time.

```csharp
tracker.FlingDetected += (s, e) =>
{
// Fling started โ€” e.Velocity.X, e.Velocity.Y in px/s
};

tracker.FlingUpdated += (s, e) =>
{
// Called each frame during fling animation
};

tracker.FlingCompleted += (s, e) =>
{
// Fling animation finished
};
```

## Drag (App-Level Object Dragging)

The tracker provides a drag lifecycle derived from pan events. Use this to move objects within your canvas (e.g., stickers, nodes in a graph editor).

```csharp
tracker.DragStarted += (s, e) =>
{
if (HitTest(e.Location) is { } item)
{
selectedItem = item;
e.Handled = true; // Prevents pan from updating the transform
}
};

tracker.DragUpdated += (s, e) =>
{
if (selectedItem != null)
{
// Convert screen delta to content coordinates
var inverse = tracker.Matrix; inverse.TryInvert(out inverse);
var contentDelta = inverse.MapVector(e.Delta.X, e.Delta.Y);
selectedItem.Position += contentDelta;
e.Handled = true;
}
};

tracker.DragEnded += (s, e) =>
{
selectedItem = null;
};
```

When `DragStarted` or `DragUpdated` sets `Handled = true`, the tracker skips its normal pan offset update **and** suppresses fling after release.

## Scroll (Mouse Wheel)

Mouse wheel zoom. Call `ProcessMouseWheel` to feed wheel events.

```csharp
tracker.ScrollDetected += (s, e) =>
{
// e.Location โ€” mouse position
// e.Delta.X, e.Delta.Y โ€” scroll amounts
};
```

## Hover

Mouse movement without any buttons pressed. Useful for cursor-based UI feedback.

```csharp
tracker.HoverDetected += (s, e) =>
{
// e.Location โ€” current mouse position
};
```

## Double Tap Zoom

By default, double-tapping zooms in by `DoubleTapZoomFactor` (2ร—). Double-tapping again at max scale resets to 1ร—. The zoom animates smoothly over `ZoomAnimationDuration` milliseconds.

To use double tap for your own logic instead, set `e.Handled = true` in your `DoubleTapDetected` handler, or disable it entirely:

```csharp
tracker.IsDoubleTapZoomEnabled = false;
```

## Lifecycle Events

```csharp
// Fired when the first finger touches down (once per gesture sequence)
tracker.GestureStarted += (s, e) => { /* gesture began */ };

// Fired when all fingers lift
tracker.GestureEnded += (s, e) => { /* gesture ended */ };

// Fired whenever the transform matrix changes (pan, zoom, rotate, fling frame)
tracker.TransformChanged += (s, e) => canvas.Invalidate();
```

## See Also

- [Gestures โ€” Quick Start](gestures.md)
- [Configuration & Customization](gesture-configuration.md)
- [API Reference โ€” SKGestureTracker](xref:SkiaSharp.Extended.SKGestureTracker)
Loading