From b33642b885c3601b90f924de348091e1e98d9a62 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 13 Oct 2016 12:31:59 -0400 Subject: [PATCH] feat(TouchHandler): Add pointerType info to touch event data (#773) Adds a pointerType field to the native event for the gesture responder handlers. Basically, e.nativeEvent.pointerType is oneOf ["mouse", "pen", "touch"]. I'll need to follow up with documentation and examples for this. Adds behavior to serialize button information from the pointer event. E.g., a right-click will result in `isRightButton: true` on the pointer event data, similar to `isLeftButton` and `isMiddleButton` for left and middle buttons respectively. When using the pen, there is `isEraser` and `isBarrelButtonPressed`, as well as a `force` field (similar to the iOS 3D touch API) for press firmness. Each of these fields are not serialized if they are `false`, which will be the case for the majority of them most of the time (i.e., there is no impact on serialized data payload for touch, but a slight penalty for enumerating the properties at serialization time). *With the exception of `isLeftButton` and `force`, which is omnipresent in all the pointer event payloads. I don't believe this is a complete solution for right-click handling, but, in theory, building a proper API to support right-click and context menus could be built as JavaScript components on top of these layers. Fixes #772 Fixes #774 --- ReactWindows/ReactNative/ReactNative.csproj | 1 + .../Touch/PointerDeviceTypeExtensions.cs | 22 +++++++ .../ReactNative/Touch/TouchHandler.cs | 63 +++++++++++++++---- 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 ReactWindows/ReactNative/Touch/PointerDeviceTypeExtensions.cs diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index f4c0d8f526..661dd5b983 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -163,6 +163,7 @@ + diff --git a/ReactWindows/ReactNative/Touch/PointerDeviceTypeExtensions.cs b/ReactWindows/ReactNative/Touch/PointerDeviceTypeExtensions.cs new file mode 100644 index 0000000000..520f69de71 --- /dev/null +++ b/ReactWindows/ReactNative/Touch/PointerDeviceTypeExtensions.cs @@ -0,0 +1,22 @@ +using Windows.Devices.Input; + +namespace ReactNative.Touch +{ + static class PointerDeviceTypeExtensions + { + public static string GetPointerDeviceTypeName(this PointerDeviceType pointerDeviceType) + { + switch (pointerDeviceType) + { + case PointerDeviceType.Touch: + return "touch"; + case PointerDeviceType.Pen: + return "pen"; + case PointerDeviceType.Mouse: + return "mouse"; + default: + return "unknown"; + } + } + } +} diff --git a/ReactWindows/ReactNative/Touch/TouchHandler.cs b/ReactWindows/ReactNative/Touch/TouchHandler.cs index caba3f75f8..3de0c7703a 100644 --- a/ReactWindows/ReactNative/Touch/TouchHandler.cs +++ b/ReactWindows/ReactNative/Touch/TouchHandler.cs @@ -4,6 +4,8 @@ using ReactNative.UIManager; using ReactNative.UIManager.Events; using System; using System.Collections.Generic; +using Windows.Foundation; +using Windows.UI.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; @@ -46,17 +48,25 @@ namespace ReactNative.Touch throw new InvalidOperationException("A pointer with this ID already exists."); } - var reactView = GetReactViewTarget(e); + var originalSource = e.OriginalSource as DependencyObject; + var rootPoint = e.GetCurrentPoint(_view); + var reactView = GetReactViewTarget(originalSource, rootPoint.Position); if (reactView != null && _view.CapturePointer(e.Pointer)) { - var reactTag = reactView.GetReactCompoundView().GetReactTagAtPoint(reactView, - e.GetCurrentPoint(reactView).Position); + var viewPoint = e.GetCurrentPoint(reactView); + var reactTag = reactView.GetReactCompoundView().GetReactTagAtPoint(reactView, viewPoint.Position); var pointer = new ReactPointer(); pointer.Target = reactTag; pointer.PointerId = e.Pointer.PointerId; pointer.Identifier = ++_pointerIDs; + pointer.PointerType = e.Pointer.PointerDeviceType.GetPointerDeviceTypeName(); + pointer.IsLeftButton = viewPoint.Properties.IsLeftButtonPressed; + pointer.IsRightButton = viewPoint.Properties.IsRightButtonPressed; + pointer.IsMiddleButton = viewPoint.Properties.IsMiddleButtonPressed; + pointer.IsHorizontalMouseWheel = viewPoint.Properties.IsHorizontalMouseWheel; + pointer.IsEraser = viewPoint.Properties.IsEraser; pointer.ReactView = reactView; - UpdatePointerForEvent(pointer, e); + UpdatePointerForEvent(pointer, rootPoint, viewPoint); var pointerIndex = _pointers.Count; _pointers.Add(pointer); @@ -123,16 +133,15 @@ namespace ReactNative.Touch return -1; } - private UIElement GetReactViewTarget(PointerRoutedEventArgs e) + private UIElement GetReactViewTarget(DependencyObject originalSource, Point point) { // If the target is not a child of the root view, then this pointer // event does not belong to React. - if (!RootViewHelper.IsReactSubview(e.OriginalSource as DependencyObject)) + if (!RootViewHelper.IsReactSubview(originalSource)) { return null; } - var point = e.GetCurrentPoint(_view).Position; var sources = VisualTreeHelper.FindElementsInHostCoordinates(point, _view); // Get the first React view that does not have pointer events set @@ -167,15 +176,23 @@ namespace ReactNative.Touch private void UpdatePointerForEvent(ReactPointer pointer, PointerRoutedEventArgs e) { - var viewPoint = e.GetCurrentPoint(_view); - var positionInRoot = viewPoint.Position; - var positionInView = e.GetCurrentPoint(pointer.ReactView).Position; + var rootPoint = e.GetCurrentPoint(_view); + var viewPoint = e.GetCurrentPoint(pointer.ReactView); + UpdatePointerForEvent(pointer, rootPoint, viewPoint); + } + + private void UpdatePointerForEvent(ReactPointer pointer, PointerPoint rootPoint, PointerPoint viewPoint) + { + var positionInRoot = rootPoint.Position; + var positionInView = viewPoint.Position; pointer.PageX = (float)positionInRoot.X; pointer.PageY = (float)positionInRoot.Y; pointer.LocationX = (float)positionInView.X; pointer.LocationY = (float)positionInView.Y; - pointer.Timestamp = viewPoint.Timestamp / 1000; // Convert microseconds to milliseconds; + pointer.Timestamp = rootPoint.Timestamp / 1000; // Convert microseconds to milliseconds; + pointer.Force = rootPoint.Properties.Pressure; + pointer.IsBarrelButtonPressed = rootPoint.Properties.IsBarrelButtonPressed; } private void DispatchTouchEvent(TouchEventType touchEventType, List activePointers, int pointerIndex) @@ -312,6 +329,30 @@ namespace ReactNative.Touch [JsonProperty(PropertyName = "pageY")] public float PageY { get; set; } + + [JsonProperty(PropertyName = "pointerType")] + public string PointerType { get; set; } + + [JsonProperty(PropertyName = "force")] + public double Force { get; set; } + + [JsonProperty(PropertyName = "isLeftButton", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsLeftButton { get; set; } + + [JsonProperty(PropertyName = "isRightButton", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsRightButton { get; set; } + + [JsonProperty(PropertyName = "isMiddleButton", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsMiddleButton { get; set; } + + [JsonProperty(PropertyName = "isBarrelButtonPressed", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsBarrelButtonPressed { get; set; } + + [JsonProperty(PropertyName = "isHorizontalScrollWheel", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsHorizontalMouseWheel { get; set; } + + [JsonProperty(PropertyName = "isEraser", DefaultValueHandling = DefaultValueHandling.Ignore)] + public bool IsEraser { get; set; } } } }