Last active
November 27, 2023 03:10
-
-
Save kellylawson/c399a46471bf22d8b6c7 to your computer and use it in GitHub Desktop.
Class to help with recognizing gestures in an InkPresenter for a Win2D canvas with custom drawing - allows 1 finger drawing while manually sending two finger gestures to a ScrollViewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Graphics.Canvas.UI.Xaml; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Windows.Foundation; | |
using Windows.UI.Input; | |
using Windows.UI.Input.Inking; | |
using Windows.UI.Xaml.Controls; | |
namespace Colorist.Drawing | |
{ | |
class TouchHandler | |
{ | |
private InkCanvas inkCanvas = null; | |
private CanvasControl canvas = null; | |
private ScrollViewer scroller = null; | |
internal List<uint> ActivePointerIds { get; set; } = new List<uint>(); | |
internal GestureRecognizer Gestures { get; set; } | |
internal IList<PointerPoint> PointerAPoints { get; set; } = new List<PointerPoint>(); | |
internal PointerPoint PrevPointerA { get; set; } = null; | |
internal PointerPoint PrevPointerB { get; set; } = null; | |
private bool IsZoom { get; set; } = false; | |
private double InitialDistanceBetweenPointers { get; set; } | |
private Point OriginalPointerCentre { get; set; } | |
public TouchHandler(CanvasControl canvasControl, InkCanvas ink, ScrollViewer scrollViewer) | |
{ | |
scroller = scrollViewer; | |
canvas = canvasControl; | |
inkCanvas = ink; | |
inkCanvas.InkPresenter.UnprocessedInput.PointerPressed += UnprocessedInput_PointerPressed; | |
inkCanvas.InkPresenter.UnprocessedInput.PointerMoved += UnprocessedInput_PointerMoved; | |
inkCanvas.InkPresenter.UnprocessedInput.PointerReleased += UnprocessedInput_PointerReleased; | |
Gestures = new GestureRecognizer(); | |
Gestures.GestureSettings = GestureSettings.ManipulationMultipleFingerPanning | | |
GestureSettings.ManipulationScale | | |
GestureSettings.ManipulationTranslateX | | |
GestureSettings.ManipulationTranslateY; | |
Gestures.ManipulationUpdated += Gestures_ManipulationUpdated; | |
scroller.LayoutUpdated += ScrollViewer_LayoutUpdated; ; | |
} | |
private void Gestures_ManipulationUpdated(GestureRecognizer sender, ManipulationUpdatedEventArgs args) | |
{ | |
if (IsZoom) | |
{ | |
scroller.ZoomToFactor(args.Delta.Scale * scroller.ZoomFactor); | |
} | |
else | |
{ | |
scroller.ScrollToHorizontalOffset(scroller.HorizontalOffset - 2 * scroller.ZoomFactor * args.Delta.Translation.X); | |
scroller.ScrollToVerticalOffset(scroller.VerticalOffset - 2 * scroller.ZoomFactor * args.Delta.Translation.Y); | |
} | |
} | |
private void ScrollViewer_LayoutUpdated(object sender, object e) | |
{ | |
if (IsZoom) | |
{ | |
if (scroller.ScrollableHeight > 0) | |
{ | |
scroller.ScrollToVerticalOffset(scroller.ScrollableHeight * (OriginalPointerCentre.Y / inkCanvas.ActualHeight)); | |
} | |
if (scroller.ScrollableWidth > 0) | |
{ | |
scroller.ScrollToHorizontalOffset(scroller.ScrollableWidth * (OriginalPointerCentre.X / inkCanvas.ActualWidth)); | |
} | |
} | |
} | |
private void UnprocessedInput_PointerPressed(InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args) | |
{ | |
ActivePointerIds.Add(args.CurrentPoint.PointerId); | |
Gestures.ProcessDownEvent(args.CurrentPoint); | |
} | |
private void UnprocessedInput_PointerMoved(InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args) | |
{ | |
IList<PointerPoint> currentPoints = args.GetIntermediatePoints(); | |
// Points come in from the runtime in reverse order, hence the .Reverse call below. | |
if (args.CurrentPoint.PointerDevice.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Touch) | |
{ | |
if (ActivePointerIds.Count == 2) | |
{ | |
if (PointerAPoints.Count > 0 && currentPoints.Count > 0 && currentPoints[0].FrameId == PointerAPoints[0].FrameId) | |
{ | |
if (PrevPointerA != null && PrevPointerB != null) | |
{ | |
if (!IsZoom && IsZoomGesture(PointerAPoints.First(), PrevPointerA, currentPoints.First(), PrevPointerB)) | |
{ | |
OriginalPointerCentre = PointerCentre(PointerAPoints.Last(), currentPoints.Last()); | |
IsZoom = true; | |
} | |
} | |
Gestures.ProcessMoveEvents(PointerAPoints); | |
try | |
{ | |
Gestures.ProcessMoveEvents(currentPoints); | |
} | |
catch (System.Runtime.InteropServices.COMException e) | |
{ | |
// bury this thing - this seems to happen with the gesturerecognizer | |
// when it processes a frame from one pointer before getting the frame for the | |
// second pointer. Ignoring it has the unfortunate side effect of ignoring some | |
// frames, but that is usually not noticeable to the user. | |
System.Diagnostics.Debug.WriteLine("exception thrown: " + e); | |
} | |
PrevPointerA = PointerAPoints.First(); | |
PrevPointerB = currentPoints.First(); | |
} | |
else | |
{ | |
PointerAPoints = new List<PointerPoint>(currentPoints); | |
} | |
} | |
} | |
} | |
private void UnprocessedInput_PointerReleased(InkUnprocessedInput sender, Windows.UI.Core.PointerEventArgs args) | |
{ | |
ActivePointerIds.Remove(args.CurrentPoint.PointerId); | |
if (ActivePointerIds.Count == 0) | |
{ | |
Gestures.CompleteGesture(); | |
PrevPointerA = null; | |
PrevPointerB = null; | |
IsZoom = false; | |
} | |
Gestures.ProcessUpEvent(args.CurrentPoint); | |
} | |
private bool IsZoomGesture(PointerPoint point1, PointerPoint prevPoint1, PointerPoint point2, PointerPoint prevPoint2) | |
{ | |
// Check the vectors of the two point movements. If they are away from each other, its zoom. | |
// If they are roughly in the same direction, it's pan. | |
Point delta1 = PointerDelta(point1, prevPoint1); | |
Point delta2 = PointerDelta(point2, prevPoint2); | |
// If the x and y go in different directions for the two pointers, its zoom | |
return !((delta1.X < 0) == (delta2.X < 0)) && !((delta1.Y < 0) == (delta2.Y < 0)); | |
} | |
private Point PointerCentre(PointerPoint point1, PointerPoint point2) | |
{ | |
double minX = Math.Min(point1.Position.X, point2.Position.X); | |
double minY = Math.Min(point1.Position.Y, point2.Position.Y); | |
double centreX = minX + Math.Abs(point1.Position.X - point2.Position.X) / 2; | |
double centreY = minY + Math.Abs(point1.Position.Y - point2.Position.Y) / 2; | |
return new Point(centreX, centreY); | |
} | |
private Point PointerDelta(PointerPoint point1, PointerPoint point2) | |
{ | |
return new Point(canvas.ConvertPixelsToDips((int)(point1.Position.X - point2.Position.X)), canvas.ConvertPixelsToDips((int)(point1.Position.Y - point2.Position.Y))); | |
} | |
public void Unload() | |
{ | |
inkCanvas.InkPresenter.UnprocessedInput.PointerPressed -= UnprocessedInput_PointerPressed; | |
inkCanvas.InkPresenter.UnprocessedInput.PointerMoved -= UnprocessedInput_PointerMoved; | |
inkCanvas.InkPresenter.UnprocessedInput.PointerReleased -= UnprocessedInput_PointerReleased; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment