Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 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
1845d16
Add Deep Zoom support using unified SKGestureTracker
mattleibow Mar 6, 2026
97ff213
Fix CI test coverage, add deep zoom docs, stop idle Blazor timer
mattleibow Mar 6, 2026
5ee567a
Add configurable SpringStiffness and SpringDampingRatio options
mattleibow Mar 6, 2026
5030da5
Fix spring animation: clamp DampingRatio≥0.01, validate MAUI bindings…
mattleibow Mar 6, 2026
a47151f
refactor: extract shared animation logic to SkiaSharp.Extended/Animat…
mattleibow Mar 6, 2026
2bee58e
test: add dedicated unit tests for SKTimerAnimation
mattleibow Mar 6, 2026
530ce45
refactor: strict separation of concerns — DeepZoom owns only tile/ren…
mattleibow Mar 6, 2026
6f254fa
fix: ViewportChanged during direct manipulation, stale csproj descrip…
mattleibow Mar 6, 2026
727c4c8
refactor: complete separation of concerns -- gesture tracker owns all…
mattleibow Mar 7, 2026
705579c
docs: fix SpringAnimator constructor and Target property in animation.md
mattleibow Mar 7, 2026
3b052a0
docs: split deep zoom into separate MAUI and Blazor pages
mattleibow Mar 7, 2026
75c644a
refactor: merge DeepZoom into core, rename to SKDeepZoom* convention,…
mattleibow Mar 11, 2026
b223b76
fix: update MAUI sample to use renamed SKDeepZoom* types
mattleibow Mar 12, 2026
0897b38
fix: remove stale ProjectReference to deleted SkiaSharp.Extended.Deep…
mattleibow Mar 12, 2026
b8c9850
fix: update DeepZoom test project to target net10.0
mattleibow Mar 12, 2026
68a031b
fix: make Fling_EventuallyCompletes test deterministic
mattleibow Mar 12, 2026
ab5cfa0
refactor: rename animation classes to SKAnimation*, remove ViewportSp…
mattleibow Mar 13, 2026
1695d5e
fix: correct ISKDeepZoomTileSource → SKDeepZoomImageSource and remove…
mattleibow Mar 13, 2026
6a8a982
feat: default deep zoom to fit mode (show full image on load)
mattleibow Mar 13, 2026
a1a33bc
fix: correct FitToView vertical centering and update tests for fit mode
mattleibow Mar 13, 2026
75187f6
refactor: extract minimal static DeepZoom system, remove custom views…
mattleibow Mar 13, 2026
4b9f938
fix: resolve deprecation warnings and fire ImageOpenFailed on load er…
mattleibow Mar 13, 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
59 changes: 59 additions & 0 deletions SkiaSharp.Extended.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,96 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Maui.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharpDemo.Blazor", "samples\SkiaSharpDemo.Blazor\SkiaSharpDemo.Blazor.csproj", "{B7E4C45C-5CAB-444E-B2D3-294151544256}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Extended.DeepZoom.Tests", "tests\SkiaSharp.Extended.DeepZoom.Tests\SkiaSharp.Extended.DeepZoom.Tests.csproj", "{34DADF5B-AF1D-4896-9925-B113B13155D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x64.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x64.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x86.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|Any CPU.Build.0 = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x64.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x64.Build.0 = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x86.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x86.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x64.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x86.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|Any CPU.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x64.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x64.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x86.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x86.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x64.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x86.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|Any CPU.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x64.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x64.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x86.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x86.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x64.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x86.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.Deploy.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x64.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x64.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x86.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x86.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x64.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x86.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|Any CPU.Build.0 = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x64.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x64.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x86.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x86.Build.0 = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|x64.ActiveCfg = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|x64.Build.0 = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|x86.ActiveCfg = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Debug|x86.Build.0 = Debug|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|Any CPU.Build.0 = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|x64.ActiveCfg = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|x64.Build.0 = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|x86.ActiveCfg = Release|Any CPU
{34DADF5B-AF1D-4896-9925-B113B13155D6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -64,6 +122,7 @@ Global
{2C67033A-2C49-4146-B942-9CDD2E0BA412} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{4B4EC78C-33B5-456D-BD7D-4358D16272F4} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
{B7E4C45C-5CAB-444E-B2D3-294151544256} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{34DADF5B-AF1D-4896-9925-B113B13155D6} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08D78153-5DD7-4C52-A348-46AA448B2CFC}
Expand Down
110 changes: 110 additions & 0 deletions docs/docs/animation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Animation System

SkiaSharp.Extended ships a small, composable animation toolkit in the `SkiaSharp.Extended` assembly. It powers the built-in gesture animations in `SKGestureTracker` but is fully independent — you can use any part on its own.

## Architecture

| Class | Role |
| :---- | :--- |
| `SKAnimationTimer` | Runs an animation loop at ~60 fps using a platform timer. Cancels itself when done. |
| `SKAnimationEasing` | Static easing functions (e.g., `CubicOut`, `Linear`). |
| `SKAnimationSpring` | Single-axis spring physics (position + velocity). |

All classes live in the `SkiaSharp.Extended` namespace.

## SKAnimationTimer

An internal animation runner used by `SKGestureTracker`. On each tick it fires a callback with the elapsed fraction (0 → 1). When the callback returns `false` the animation stops.

```csharp
// SKAnimationTimer is internal — use it via SKGestureTracker, or via SKAnimationEasing for your own easing.
// To run your own animation, simply use a platform timer + SKAnimationEasing:

private async void AnimateSomething()
{
var start = DateTimeOffset.UtcNow;
var duration = TimeSpan.FromMilliseconds(300);

while (true)
{
var t = (DateTimeOffset.UtcNow - start).TotalSeconds / duration.TotalSeconds;
if (t >= 1.0) { t = 1.0; break; }

var eased = SKAnimationEasing.CubicOut(t);
ApplyValue(Lerp(fromValue, toValue, eased));

await Task.Delay(16); // ~60 fps
}
ApplyValue(toValue);
}
```

## SKAnimationEasing

A static class providing common easing curves. All functions take a `double t` in `[0, 1]` and return an eased value in approximately `[0, 1]`.

```csharp
using SkiaSharp.Extended;

// Ease-out cubic: fast start, slows near end (the default for double-tap zoom)
double eased = SKAnimationEasing.CubicOut(t);

// Ease-in-out cubic: slow start, fast middle, slow end
double eased = SKAnimationEasing.CubicInOut(t);

// Linear (no easing)
double eased = SKAnimationEasing.Linear(t);
```

All easing functions are pure static methods — no allocation, no state.

## SKAnimationSpring

A single-axis spring simulation. Useful for smooth animated transitions where you want physical feel (configurable stiffness and damping).

```csharp
using SkiaSharp.Extended;

var spring = new SKAnimationSpring(0.0); // initialValue
spring.Stiffness = 100.0;
spring.DampingRatio = 1.0;

// Set target (current position stays where it is, velocity carries through)
spring.Target = 1.0;

// Advance simulation (call ~60 fps)
spring.Update(deltaTime: 0.016); // seconds

// Read current state
double position = spring.Current;
bool settled = spring.IsSettled;
```

**Properties:**
- `Stiffness` — how fast the spring snaps to target (higher = faster). Default `100.0`.
- `DampingRatio` — `1.0` = critically damped (no overshoot), `< 1.0` = bouncy, `> 1.0` = sluggish.
- `Target` — the target value the spring is animating toward.
- `Current` — the current interpolated value.
- `IsSettled` — `true` when position and velocity are both near zero.

## Relationship to SKGestureTracker

`SKGestureTracker` uses `SKAnimationTimer` and `SKAnimationEasing` internally to animate fling deceleration and double-tap zoom. These animations fire `TransformChanged` events on each frame — you don't need to manage a timer yourself.

```
Touch events
SKGestureTracker ← owns SKAnimationTimer + SKAnimationEasing
↓ TransformChanged (every frame while animating)
Your code → update viewport / deep zoom controller / canvas
↓ InvalidateSurface
Canvas repaint
```

## Next Steps

- [Gestures](gestures.md) — SKGestureTracker and how animation integrates with gesture handling
- [Gesture Configuration](gesture-configuration.md) — Fine-tune zoom speed, fling behavior, and easing
- [Deep Zoom](deep-zoom.md) — How animation and gestures compose with tile loading
- [API Reference — SKAnimationEasing](xref:SkiaSharp.Extended.SKAnimationEasing)
- [API Reference — SKAnimationSpring](xref:SkiaSharp.Extended.SKAnimationSpring)
83 changes: 83 additions & 0 deletions docs/docs/deep-zoom-blazor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Deep Zoom for Blazor

Use `SKDeepZoomController` with a plain `SKCanvasView` to render Deep Zoom images in Blazor WebAssembly. There is no custom component — the page wires the services directly to the canvas.

## Quick Start

```razor
@page "/deepzoom"
@implements IDisposable
@inject HttpClient Http
@using SkiaSharp.Extended.DeepZoom

<SKCanvasView @ref="_canvas"
OnPaintSurface="OnPaintSurface"
style="width: 100%; height: 600px; border: 1px solid #ccc;" />

@code {
private SKCanvasView? _canvas;
private SKDeepZoomController? _controller;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;

_controller = new SKDeepZoomController();
_controller.InvalidateRequired += OnInvalidateRequired;

var xml = await Http.GetStringAsync("deepzoom/image.dzi");
var baseUrl = new Uri(Http.BaseAddress!, "deepzoom/image_files/").ToString();
var tileSource = SKDeepZoomImageSource.Parse(xml, baseUrl);

_controller.Load(tileSource, new SKDeepZoomHttpTileFetcher(new HttpClient()));

await InvokeAsync(StateHasChanged);
}

private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
if (_controller == null) return;

_controller.SetControlSize(e.Info.Width, e.Info.Height);
_controller.Update();
_controller.Render(e.Surface.Canvas);
}

private void OnInvalidateRequired(object? sender, EventArgs e)
=> InvokeAsync(() => _canvas?.Invalidate());

public void Dispose()
{
if (_controller != null)
{
_controller.InvalidateRequired -= OnInvalidateRequired;
_controller.Dispose();
}
}
}
```

## Serving Tile Assets

Place `.dzi` and tile folder under `wwwroot`. In the project file, reference them as content:

```xml
<ItemGroup>
<Content Include="wwwroot\deepzoom\image.dzi" />
<Content Include="wwwroot\deepzoom\image_files\**" />
</ItemGroup>
```

The `SKDeepZoomHttpTileFetcher` fetches each tile URL via `HttpClient`; tile URLs are constructed automatically from the base URL you supply to `SKDeepZoomImageSource.Parse`.

## Rendering Behaviour

- **Fit and center**: On load the controller calls `FitToView()` so the full image is visible and centered in the canvas. Neither cropping nor distortion occurs.
- **Tile resolution**: `Update()` selects the pyramid level whose tile size best matches the physical pixel dimensions of the canvas. Only visible tiles are requested.
- **Tile blending**: While high-resolution tiles are in-flight, parent-level tiles are upscaled and drawn as placeholders.

## Related

- [Deep Zoom overview](deep-zoom.md) — architecture, services, and API reference
- [Deep Zoom for MAUI](deep-zoom-maui.md) — .NET MAUI integration

Loading
Loading