From a766d1a9ee4eb10199430b947de28fc9df2dff85 Mon Sep 17 00:00:00 2001 From: Adam Comella Date: Sun, 7 Aug 2016 07:48:41 -0700 Subject: [PATCH] Fix onPress handlers on nested Text components (#560) Currently, onPress handlers only work on outermost Text components. This change enables you to place onPress handlers on nested Text components. For example, this now works: Press Me In the fix, we need to manually do hit testing on outermost Text components to see if any of the nested Text components got hit. This is because the nested Text components are represented by Inlines rather than by UIElements. --- ReactWindows/ReactNative/ReactNative.csproj | 3 ++ .../ReactNative/Touch/TouchHandler.cs | 5 ++- .../UIManager/DependencyObjectExtensions.cs | 43 +++++++++++++++++++ .../UIManager/IReactCompoundView.cs | 28 ++++++++++++ .../UIManager/ReactDefaultCompoundView.cs | 18 ++++++++ .../Views/Text/ReactTextCompoundView.cs | 22 ++++++++++ .../Views/Text/ReactTextViewManager.cs | 2 + 7 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 ReactWindows/ReactNative/UIManager/IReactCompoundView.cs create mode 100644 ReactWindows/ReactNative/UIManager/ReactDefaultCompoundView.cs create mode 100644 ReactWindows/ReactNative/Views/Text/ReactTextCompoundView.cs diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 45dc1dbee9f..f1fc0501fe5 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -272,7 +272,9 @@ + + @@ -362,6 +364,7 @@ + diff --git a/ReactWindows/ReactNative/Touch/TouchHandler.cs b/ReactWindows/ReactNative/Touch/TouchHandler.cs index 77227e2d2f4..e347e885fe7 100644 --- a/ReactWindows/ReactNative/Touch/TouchHandler.cs +++ b/ReactWindows/ReactNative/Touch/TouchHandler.cs @@ -46,10 +46,13 @@ private void OnPointerPressed(object sender, PointerRoutedEventArgs e) } var reactView = GetReactViewFromView(e.OriginalSource as UIElement); + var reactTag = reactView.GetReactCompoundView().GetReactTagAtPoint(reactView, + e.GetCurrentPoint(reactView).Position); + if (reactView != null && _view.CapturePointer(e.Pointer)) { var pointer = new ReactPointer(); - pointer.Target = reactView.GetTag(); + pointer.Target = reactTag; pointer.PointerId = e.Pointer.PointerId; pointer.Identifier = ++_pointerIDs; pointer.ReactView = reactView; diff --git a/ReactWindows/ReactNative/UIManager/DependencyObjectExtensions.cs b/ReactWindows/ReactNative/UIManager/DependencyObjectExtensions.cs index 7bbd14b8494..8ace3fe8997 100644 --- a/ReactWindows/ReactNative/UIManager/DependencyObjectExtensions.cs +++ b/ReactWindows/ReactNative/UIManager/DependencyObjectExtensions.cs @@ -11,6 +11,7 @@ public static class DependencyObjectExtensions { private static readonly ConditionalWeakTable s_properties = new ConditionalWeakTable(); + private static readonly IReactCompoundView s_defaultCompoundView = new ReactDefaultCompoundView(); /// /// Sets the pointer events for the view. @@ -44,6 +45,46 @@ public static PointerEvents GetPointerEvents(this DependencyObject view) return elementData.PointerEvents.Value; } + /// + /// Associates an implementation of IReactCompoundView with the view. + /// + /// The view. + /// The implementation of IReactCompoundView. + public static void SetReactCompoundView(this DependencyObject view, IReactCompoundView compoundView) + { + if (view == null) + throw new ArgumentNullException(nameof(view)); + + s_properties.GetOrCreateValue(view).CompoundView = compoundView; + } + + /// + /// Gets the implementation of IReactCompoundView associated with the view. + /// + /// The view. + /// + /// The implementation of IReactCompoundView associated with the view. Defaults to + /// an instance of ReactDefaultCompoundView when no other implementation has been + /// provided. + /// + public static IReactCompoundView GetReactCompoundView(this DependencyObject view) + { + if (view == null) + throw new ArgumentNullException(nameof(view)); + + var elementData = default(DependencyObjectData); + if (s_properties.TryGetValue(view, out elementData)) + { + var compoundView = elementData.CompoundView; + if (compoundView != null) + { + return compoundView; + } + } + + return s_defaultCompoundView; + } + internal static void SetTag(this DependencyObject view, int tag) { if (view == null) @@ -115,6 +156,8 @@ class DependencyObjectData public PointerEvents? PointerEvents { get; set; } public int? Tag { get; set; } + + public IReactCompoundView CompoundView { get; set; } } } } diff --git a/ReactWindows/ReactNative/UIManager/IReactCompoundView.cs b/ReactWindows/ReactNative/UIManager/IReactCompoundView.cs new file mode 100644 index 00000000000..7c97ff99731 --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/IReactCompoundView.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; + +namespace ReactNative.UIManager +{ + /// + /// Interface consisting of methods which are relevant to views which contain + /// visuals that have react tags but are not rendered using UIElements. + /// + public interface IReactCompoundView + { + /// + /// Returns the react tag rendered at point in reactView. The view + /// is not expected to do hit testing on its UIElement descendants. Rather, + /// this is useful for views which are composed of visuals that are associated + /// with react tags but the visuals are not UIElements. + /// + /// The react view to do hit testing within. + /// The point to hit test in coordinates that are relative to the view. + /// The react tag rendered at point in reactView. + int GetReactTagAtPoint(UIElement reactView, Point point); + } +} diff --git a/ReactWindows/ReactNative/UIManager/ReactDefaultCompoundView.cs b/ReactWindows/ReactNative/UIManager/ReactDefaultCompoundView.cs new file mode 100644 index 00000000000..25daaddb1e4 --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/ReactDefaultCompoundView.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; + +namespace ReactNative.UIManager +{ + class ReactDefaultCompoundView : IReactCompoundView + { + public int GetReactTagAtPoint(UIElement reactView, Point point) + { + return reactView.GetTag(); + } + } +} diff --git a/ReactWindows/ReactNative/Views/Text/ReactTextCompoundView.cs b/ReactWindows/ReactNative/Views/Text/ReactTextCompoundView.cs new file mode 100644 index 00000000000..58c65f1edcf --- /dev/null +++ b/ReactWindows/ReactNative/Views/Text/ReactTextCompoundView.cs @@ -0,0 +1,22 @@ +using ReactNative.UIManager; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.Foundation; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace ReactNative.Views.Text +{ + class ReactTextCompoundView : IReactCompoundView + { + public int GetReactTagAtPoint(UIElement reactView, Point point) + { + var richTextBlock = reactView.As(); + var textPointer = richTextBlock.GetPositionFromPoint(point); + return textPointer.Parent.GetTag(); + } + } +} diff --git a/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs b/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs index e5755c165b1..a46ee3ef435 100644 --- a/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs +++ b/ReactWindows/ReactNative/Views/Text/ReactTextViewManager.cs @@ -13,6 +13,7 @@ namespace ReactNative.Views.Text /// public class ReactTextViewManager : ViewParentManager { + private static readonly IReactCompoundView s_compoundView = new ReactTextCompoundView(); private const double DefaultFontSize = 15; /// @@ -162,6 +163,7 @@ protected override RichTextBlock CreateViewInstance(ThemedReactContext reactCont }; richTextBlock.Blocks.Add(new Paragraph()); + richTextBlock.SetReactCompoundView(s_compoundView); return richTextBlock; }