diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SKTouchHandler.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SKTouchHandler.cs new file mode 100644 index 00000000..638ba5be --- /dev/null +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SKTouchHandler.cs @@ -0,0 +1,91 @@ +using System; +using Android.Views; + +namespace SkiaSharp.Views.Forms +{ + internal class SKTouchHandler + { + private Action onTouchAction; + private Func scalePixels; + + public SKTouchHandler(Action onTouchAction, Func scalePixels) + { + this.onTouchAction = onTouchAction; + this.scalePixels = scalePixels; + } + + public void Attach(View view) + { + view.Touch += OnTouch; + } + + public void Detach(View view) + { + // clean the view + if (view != null) + { + view.Touch -= OnTouch; + } + + // remove references + onTouchAction = null; + scalePixels = null; + } + + private void OnTouch(object sender, View.TouchEventArgs e) + { + if (onTouchAction == null || scalePixels == null) + return; + + var evt = e.Event; + var pointer = evt.ActionIndex; + + var id = evt.GetPointerId(pointer); + var coords = new SKPoint(scalePixels(evt.GetX(pointer)), scalePixels(evt.GetY(pointer))); + + switch (evt.ActionMasked) + { + case MotionEventActions.Down: + case MotionEventActions.PointerDown: + { + var args = new SKTouchActionEventArgs(id, SKTouchActionType.Pressed, coords); + onTouchAction(args); + e.Handled = args.Handled; + break; + } + + case MotionEventActions.Move: + { + var count = evt.PointerCount; + for (pointer = 0; pointer < count; pointer++) + { + id = evt.GetPointerId(pointer); + coords = new SKPoint(scalePixels(evt.GetX(pointer)), scalePixels(evt.GetY(pointer))); + + var args = new SKTouchActionEventArgs(id, SKTouchActionType.Moved, coords); + onTouchAction(args); + e.Handled = e.Handled || args.Handled; + } + break; + } + + case MotionEventActions.Up: + case MotionEventActions.PointerUp: + { + var args = new SKTouchActionEventArgs(id, SKTouchActionType.Released, coords); + onTouchAction(args); + e.Handled = args.Handled; + break; + } + + case MotionEventActions.Cancel: + { + var args = new SKTouchActionEventArgs(id, SKTouchActionType.Cancelled, coords); + onTouchAction(args); + e.Handled = args.Handled; + break; + } + } + } + } +} diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SkiaSharp.Views.Forms.Android.csproj b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SkiaSharp.Views.Forms.Android.csproj index 372e1bb7..95f51a16 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SkiaSharp.Views.Forms.Android.csproj +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Android/SkiaSharp.Views.Forms.Android.csproj @@ -85,6 +85,7 @@ + diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SKTouchHandler.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SKTouchHandler.cs new file mode 100644 index 00000000..97b7f1da --- /dev/null +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SKTouchHandler.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using Foundation; +using AppKit; + +namespace SkiaSharp.Views.Forms +{ + internal class SKTouchHandler : NSGestureRecognizer + { + private Action onTouchAction; + private Func scalePixels; + + public SKTouchHandler(Action onTouchAction, Func scalePixels) + { + this.onTouchAction = onTouchAction; + this.scalePixels = scalePixels; + } + + public void Attach(NSView view) + { + view.AddGestureRecognizer(this); + } + + public void Detach(NSView view) + { + // clean the view + if (view != null) + { + view.RemoveGestureRecognizer(this); + } + + // remove references + onTouchAction = null; + scalePixels = null; + } + + public override void MouseDown(NSEvent mouseEvent) + { + base.MouseDown(mouseEvent); + + FireEvent(SKTouchActionType.Pressed, mouseEvent); + } + + public override void MouseUp(NSEvent mouseEvent) + { + base.MouseUp(mouseEvent); + + FireEvent(SKTouchActionType.Released, mouseEvent); + } + + public override void MouseDragged(NSEvent mouseEvent) + { + base.MouseDragged(mouseEvent); + + FireEvent(SKTouchActionType.Moved, mouseEvent); + } + + private bool FireEvent(SKTouchActionType actionType, NSEvent mouseEvent) + { + if (onTouchAction == null || scalePixels == null) + return false; + + var id = mouseEvent.ButtonNumber; + + var cgPoint = LocationInView(View); + // flip the Y coordinate for macOS + cgPoint.Y = View.Bounds.Height - cgPoint.Y; + + var point = new SKPoint((float)scalePixels(cgPoint.X), (float)scalePixels(cgPoint.Y)); + + var args = new SKTouchActionEventArgs(id, actionType, point); + onTouchAction(args); + return args.Handled; + } + } +} diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SkiaSharp.Views.Forms.Mac.csproj b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SkiaSharp.Views.Forms.Mac.csproj index 117df41d..e845d3ff 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SkiaSharp.Views.Forms.Mac.csproj +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Mac/SkiaSharp.Views.Forms.Mac.csproj @@ -61,6 +61,7 @@ + diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKCanvasViewRendererBase.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKCanvasViewRendererBase.cs index 7c136123..6a7224c5 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKCanvasViewRendererBase.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKCanvasViewRendererBase.cs @@ -27,6 +27,25 @@ namespace SkiaSharp.Views.Forms where TFormsView : SKFormsView where TNativeView : SKNativeView { + private readonly SKTouchHandler touchHandler; + + public SKCanvasViewRendererBase() + { +#if __ANDROID__ + touchHandler = new SKTouchHandler( + args => ((ISKCanvasViewController)Element).OnTouchAction(args), + coord => Element.IgnorePixelScaling ? (float)Context.FromPixels(coord) : coord); +#elif __IOS__ + touchHandler = new SKTouchHandler( + args => ((ISKCanvasViewController)Element).OnTouchAction(args), + coord => Element.IgnorePixelScaling ? coord : coord * Control.ContentScaleFactor); +#elif __MACOS__ + touchHandler = new SKTouchHandler( + args => ((ISKCanvasViewController)Element).OnTouchAction(args), + coord => Element.IgnorePixelScaling ? coord : coord * Control.Window.BackingScaleFactor); +#endif + } + protected override void OnElementChanged(ElementChangedEventArgs e) { if (e.OldElement != null) @@ -47,6 +66,7 @@ namespace SkiaSharp.Views.Forms { var view = CreateNativeControl(); view.PaintSurface += OnPaintSurface; + touchHandler.Attach(view); SetNativeControl(view); } @@ -102,6 +122,9 @@ namespace SkiaSharp.Views.Forms control.PaintSurface -= OnPaintSurface; } + // detach, regardless of state + touchHandler.Detach(control); + base.Dispose(disposing); } diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKGLViewRendererBase.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKGLViewRendererBase.cs index 82f7c811..a8113f5d 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKGLViewRendererBase.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Native.Shared/SKGLViewRendererBase.cs @@ -28,6 +28,15 @@ namespace SkiaSharp.Views.Forms where TFormsView : SKFormsView where TNativeView : SKNativeView { + private readonly SKTouchHandler touchHandler; + + public SKGLViewRendererBase() + { + touchHandler = new SKTouchHandler( + args => ((ISKGLViewController)Element).OnTouchAction(args), + coord => coord); + } + protected override void OnElementChanged(ElementChangedEventArgs e) { if (e.OldElement != null) @@ -52,6 +61,7 @@ namespace SkiaSharp.Views.Forms #else view.PaintSurface += OnPaintSurface; #endif + touchHandler.Attach(view); SetNativeControl(view); } @@ -108,6 +118,9 @@ namespace SkiaSharp.Views.Forms #endif } + // detach, regardless of state + touchHandler.Detach(control); + base.Dispose(disposing); } diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKCanvasView.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKCanvasView.cs index eab056db..f0618562 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKCanvasView.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKCanvasView.cs @@ -12,6 +12,9 @@ namespace SkiaSharp.Views.Forms // the user can subscribe to repaint public event EventHandler PaintSurface; + // the user can subscribe to touch events + public event EventHandler TouchAction; + // the native listens to this event private event EventHandler SurfaceInvalidated; private event EventHandler GetCanvasSize; @@ -47,6 +50,12 @@ namespace SkiaSharp.Views.Forms PaintSurface?.Invoke(this, e); } + // the native view responds to a touch + protected virtual void OnTouchAction(SKTouchActionEventArgs e) + { + TouchAction?.Invoke(this, e); + } + // ISKViewController implementation event EventHandler ISKCanvasViewController.SurfaceInvalidated @@ -66,6 +75,11 @@ namespace SkiaSharp.Views.Forms OnPaintSurface(e); } + void ISKCanvasViewController.OnTouchAction(SKTouchActionEventArgs e) + { + OnTouchAction(e); + } + protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { return new SizeRequest(new Size(40.0, 40.0)); @@ -80,5 +94,8 @@ namespace SkiaSharp.Views.Forms // the native view tells the user to repaint void OnPaintSurface(SKPaintSurfaceEventArgs e); + + // the native view responds to a touch + void OnTouchAction(SKTouchActionEventArgs e); } } diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKGLView.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKGLView.cs index 61515e61..bc7121c9 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKGLView.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKGLView.cs @@ -17,6 +17,9 @@ namespace SkiaSharp.Views.Forms // the user can subscribe to repaint public event EventHandler PaintSurface; + + // the user can subscribe to touch events + public event EventHandler TouchAction; // the native listens to this event private event EventHandler SurfaceInvalidated; @@ -46,6 +49,12 @@ namespace SkiaSharp.Views.Forms { PaintSurface?.Invoke(this, e); } + + // the native view responds to a touch + protected virtual void OnTouchAction(SKTouchActionEventArgs e) + { + TouchAction?.Invoke(this, e); + } // ISKViewController implementation @@ -66,6 +75,11 @@ namespace SkiaSharp.Views.Forms OnPaintSurface(e); } + void ISKGLViewController.OnTouchAction(SKTouchActionEventArgs e) + { + OnTouchAction(e); + } + protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint) { return new SizeRequest(new Size(40.0, 40.0)); @@ -80,5 +94,8 @@ namespace SkiaSharp.Views.Forms // the native view tells the user to repaint void OnPaintSurface(SKPaintGLSurfaceEventArgs e); + + // the native view responds to a touch + void OnTouchAction(SKTouchActionEventArgs e); } } diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKTouchActionEventArgs.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKTouchActionEventArgs.cs new file mode 100644 index 00000000..5d074345 --- /dev/null +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SKTouchActionEventArgs.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; + +namespace SkiaSharp.Views.Forms +{ + public class SKTouchActionEventArgs : EventArgs + { + public SKTouchActionEventArgs(long id, SKTouchActionType type, SKPoint[] locations) + { + Id = id; + Type = type; + Locations = locations ?? new SKPoint[0]; + Handled = true; + } + + public SKTouchActionEventArgs(long id, SKTouchActionType type, SKPoint location) + { + Id = id; + Type = type; + Locations = new[] { location }; + Handled = true; + } + + // this may be removed, but for now keep it + internal bool Handled { get; set; } + + public long Id { get; private set; } + + public SKTouchActionType Type { get; private set; } + + public SKPoint[] Locations { get; private set; } + + public SKPoint Location => Locations.FirstOrDefault(); + + public bool InContact => Type == SKTouchActionType.Pressed || Type == SKTouchActionType.Moved; + } + + public enum SKTouchActionType + { + Pressed, + Moved, + Released, + Cancelled + } +} diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SkiaSharp.Views.Forms.Shared.projitems b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SkiaSharp.Views.Forms.Shared.projitems index 56bae60a..d2e45c54 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SkiaSharp.Views.Forms.Shared.projitems +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.Shared/SkiaSharp.Views.Forms.Shared.projitems @@ -17,5 +17,6 @@ + \ No newline at end of file diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKCanvasViewRenderer.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKCanvasViewRenderer.cs index fd6f7c84..27bc15b5 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKCanvasViewRenderer.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKCanvasViewRenderer.cs @@ -13,7 +13,6 @@ namespace SkiaSharp.Views.Forms { var view = base.CreateNativeControl(); - view.UserInteractionEnabled = false; // Force the opacity to false for consistency with the other platforms view.Opaque = false; diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKGLViewRenderer.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKGLViewRenderer.cs index a212fb4d..3a7544e0 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKGLViewRenderer.cs +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKGLViewRenderer.cs @@ -17,7 +17,6 @@ namespace SkiaSharp.Views.Forms { var view = base.CreateNativeControl(); - view.UserInteractionEnabled = false; // Force the opacity to false for consistency with the other platforms view.Opaque = false; diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKTouchHandler.cs b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKTouchHandler.cs new file mode 100644 index 00000000..f65806f7 --- /dev/null +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SKTouchHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using Foundation; +using UIKit; + +namespace SkiaSharp.Views.Forms +{ + internal class SKTouchHandler : UIGestureRecognizer + { + private Action onTouchAction; + private Func scalePixels; + + public SKTouchHandler(Action onTouchAction, Func scalePixels) + { + this.onTouchAction = onTouchAction; + this.scalePixels = scalePixels; + } + + public void Attach(UIView view) + { + view.AddGestureRecognizer(this); + } + + public void Detach(UIView view) + { + // clean the view + if (view != null) + { + view.RemoveGestureRecognizer(this); + } + + // remove references + onTouchAction = null; + scalePixels = null; + } + + public override void TouchesBegan(NSSet touches, UIEvent evt) + { + base.TouchesBegan(touches, evt); + + foreach (UITouch touch in touches.Cast()) + { + FireEvent(SKTouchActionType.Pressed, touch); + } + } + + public override void TouchesMoved(NSSet touches, UIEvent evt) + { + base.TouchesMoved(touches, evt); + + foreach (UITouch touch in touches.Cast()) + { + FireEvent(SKTouchActionType.Moved, touch); + } + } + + public override void TouchesEnded(NSSet touches, UIEvent evt) + { + base.TouchesEnded(touches, evt); + + foreach (UITouch touch in touches.Cast()) + { + FireEvent(SKTouchActionType.Released, touch); + } + } + + public override void TouchesCancelled(NSSet touches, UIEvent evt) + { + base.TouchesCancelled(touches, evt); + + foreach (UITouch touch in touches.Cast()) + { + FireEvent(SKTouchActionType.Cancelled, touch); + } + } + + private bool FireEvent(SKTouchActionType actionType, UITouch touch) + { + if (onTouchAction == null || scalePixels == null) + return false; + + var id = touch.Handle.ToInt64(); + + var cgPoint = touch.LocationInView(View); + var point = new SKPoint((float)scalePixels(cgPoint.X), (float)scalePixels(cgPoint.Y)); + + var args = new SKTouchActionEventArgs(id, actionType, point); + onTouchAction(args); + return args.Handled; + } + } +} diff --git a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SkiaSharp.Views.Forms.iOS.csproj b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SkiaSharp.Views.Forms.iOS.csproj index 0a2919eb..9cf32aa0 100644 --- a/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SkiaSharp.Views.Forms.iOS.csproj +++ b/source/SkiaSharp.Views.Forms/SkiaSharp.Views.Forms.iOS/SkiaSharp.Views.Forms.iOS.csproj @@ -69,6 +69,7 @@ +