Skip to content

Commit f3f1931

Browse files
authored
fix(TouchHandler): Consider all possible views for click target (microsoft#736)
Previously, we were only considering the hierarchy from the pointer "OriginalSource" in the pointer events calculations. Unfortunately, this meant that if a transparent overlay with "box-none" or "none" pointer events was set, as is the case for "react-native-drawer", then no alternative targets would be considered. This change uses VisualTreeHelper.FindElementsInHostCoordinates to consider all possible targets. Fixes microsoft#715
1 parent ad8c9ef commit f3f1931

File tree

2 files changed

+64
-26
lines changed

2 files changed

+64
-26
lines changed

ReactWindows/ReactNative/Touch/TouchHandler.cs

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using ReactNative.UIManager.Events;
55
using System;
66
using System.Collections.Generic;
7+
using Windows.Foundation;
78
using Windows.UI.Xaml;
89
using Windows.UI.Xaml.Input;
10+
using Windows.UI.Xaml.Media;
911

1012
namespace ReactNative.Touch
1113
{
@@ -45,8 +47,7 @@ private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
4547
throw new InvalidOperationException("A pointer with this ID already exists.");
4648
}
4749

48-
var reactView = GetReactViewFromView(e.OriginalSource as UIElement);
49-
50+
var reactView = GetReactViewFromPoint(e.GetCurrentPoint(_view).Position);
5051
if (reactView != null && _view.CapturePointer(e.Pointer))
5152
{
5253
var reactTag = reactView.GetReactCompoundView().GetReactTagAtPoint(reactView,
@@ -123,31 +124,38 @@ private int IndexOfPointerWithId(uint pointerId)
123124
return -1;
124125
}
125126

126-
private UIElement GetReactViewFromView(DependencyObject originalSource)
127+
private UIElement GetReactViewFromPoint(Point point)
127128
{
128-
var viewHierarchy = RootViewHelper.GetReactViewHierarchy(originalSource);
129-
if (viewHierarchy.Count == 0)
130-
{
131-
return null;
132-
}
129+
var sources = VisualTreeHelper.FindElementsInHostCoordinates(point, _view);
130+
131+
// Get the first React view that does not have pointer events set
132+
// to 'none' or 'box-none', and is not a child of a view with
133+
// 'box-only' or 'none' settings for pointer events.
133134

134-
var target = -1;
135-
for (var i = 0; i < viewHierarchy.Count; ++i)
135+
// TODO: use pooled data structure
136+
var isBoxOnlyCache = new Dictionary<UIElement, bool>();
137+
foreach (var source in sources)
136138
{
137-
var view = viewHierarchy[i];
138-
var pointerEvents = view.GetPointerEvents();
139-
if (pointerEvents != PointerEvents.None && pointerEvents != PointerEvents.BoxNone)
139+
if (!source.HasTag())
140+
{
141+
continue;
142+
}
143+
144+
var pointerEvents = source.GetPointerEvents();
145+
if (pointerEvents == PointerEvents.None || pointerEvents == PointerEvents.BoxNone)
140146
{
141-
target = i;
147+
continue;
142148
}
143149

144-
if (pointerEvents == PointerEvents.BoxOnly || pointerEvents == PointerEvents.None)
150+
var viewHierarchy = RootViewHelper.GetReactViewHierarchy(source);
151+
var isBoxOnly = IsBoxOnlyWithCache(viewHierarchy, isBoxOnlyCache);
152+
if (!isBoxOnly)
145153
{
146-
break;
154+
return source;
147155
}
148156
}
149157

150-
return target < 0 ? null : viewHierarchy[target];
158+
return null;
151159
}
152160

153161
private void UpdatePointerForEvent(ReactPointer pointer, PointerRoutedEventArgs e)
@@ -184,6 +192,42 @@ private void DispatchTouchEvent(TouchEventType touchEventType, List<ReactPointer
184192
.DispatchEvent(touchEvent);
185193
}
186194

195+
private static bool IsBoxOnlyWithCache(IEnumerable<UIElement> hierarchy, IDictionary<UIElement, bool> cache)
196+
{
197+
var enumerator = hierarchy.GetEnumerator();
198+
199+
// Skip the first element (only checking ancestors)
200+
if (!enumerator.MoveNext())
201+
{
202+
return false;
203+
}
204+
205+
return IsBoxOnlyWithCacheRecursive(enumerator, cache);
206+
}
207+
208+
private static bool IsBoxOnlyWithCacheRecursive(IEnumerator<UIElement> enumerator, IDictionary<UIElement, bool> cache)
209+
{
210+
if (!enumerator.MoveNext())
211+
{
212+
return false;
213+
}
214+
215+
var currentView = enumerator.Current;
216+
var isBoxOnly = default(bool);
217+
if (!cache.TryGetValue(currentView, out isBoxOnly))
218+
{
219+
var pointerEvents = currentView.GetPointerEvents();
220+
221+
isBoxOnly = pointerEvents == PointerEvents.BoxOnly
222+
|| pointerEvents == PointerEvents.None
223+
|| IsBoxOnlyWithCacheRecursive(enumerator, cache);
224+
225+
cache.Add(currentView, isBoxOnly);
226+
}
227+
228+
return isBoxOnly;
229+
}
230+
187231
class TouchEvent : Event
188232
{
189233
private readonly TouchEventType _touchEventType;

ReactWindows/ReactNative/UIManager/RootViewHelper.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,11 @@ public static ReactRootView GetRootView(DependencyObject view)
3636
}
3737

3838
/// <summary>
39-
/// Returns the list of pointer events views in the hierarchy, starting
40-
/// from the root view.
39+
/// Gets the hierarchy of React views from the given view.
4140
/// </summary>
4241
/// <param name="view">The view.</param>
43-
/// <returns>The pointer events hierarchy.</returns>
44-
public static IList<UIElement> GetReactViewHierarchy(DependencyObject view)
45-
{
46-
return GetReactViewHierarchyCore(view).Reverse().ToList();
47-
}
48-
49-
private static IEnumerable<UIElement> GetReactViewHierarchyCore(DependencyObject view)
42+
/// <returns>The view hierarchy.</returns>
43+
public static IEnumerable<UIElement> GetReactViewHierarchy(DependencyObject view)
5044
{
5145
var current = view;
5246
while (true)

0 commit comments

Comments
 (0)