Skip to content

Instantly share code, notes, and snippets.

@kellylawson
Last active November 27, 2023 03:10
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kellylawson/c399a46471bf22d8b6c7 to your computer and use it in GitHub Desktop.
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
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