private void UpdatePointersInViews(UIElement reactView, PointerPoint rootPoint, PointerRoutedEventArgs e)
        {
            // Create list of react views that should be tracked based on the
            // view hierarchy starting from reactView, keeping in just the views that intersect the rootPoint
            var newViews = reactView != null ? new HashSet <DependencyObject>(
                RootViewHelper.GetReactViewHierarchy(reactView).Where((v) =>
            {
                if (v is FrameworkElement element)
                {
                    var viewPoint = e.GetCurrentPoint(element);
                    return(viewPoint.Position.X >= 0 && viewPoint.Position.X < element.ActualWidth &&
                           viewPoint.Position.Y >= 0 && viewPoint.Position.Y < element.ActualHeight);
                }
                else
                {
                    return(false);
                }
            })) :
                           new HashSet <DependencyObject>();

            // Get existing list of react views for the pointer id
            HashSet <DependencyObject> existingViews;

            if (!_pointersInViews.TryGetValue(e.Pointer.PointerId, out existingViews))
            {
                existingViews = new HashSet <DependencyObject>();
            }

            // Return quick if list didn't change
            if (newViews.SetEquals(existingViews))
            {
                return;
            }

            // Notify the tags that disappeared from the list if:
            // - there's still a tag associated with the view (it hasn't been removed from tree)
            // - there's a need (driven by handlers hooked on the JS side) to send events
            foreach (var existingView in existingViews)
            {
                if (!newViews.Contains(existingView) &&
                    existingView.HasTag() &&
                    ShouldSendPointerEnterLeaveOverOutEvent(existingView, out var enterOrLeave, out var overOrOut))
                {
                    OnPointerEnteredExited(TouchEventType.Exited, (UIElement)existingView, rootPoint, e, enterOrLeave, overOrOut);
                }
            }

            // Notify the new views that showed up if:
            // - there's a need (driven by handlers hooked on the JS side) to send events
            foreach (var newView in newViews)
            {
                if (!existingViews.Contains(newView) &&
                    ShouldSendPointerEnterLeaveOverOutEvent(newView, out var enterOrLeave, out var overOrOut))
                {
                    OnPointerEnteredExited(TouchEventType.Entered, (UIElement)newView, rootPoint, e, enterOrLeave, overOrOut);
                }
            }

            _pointersInViews[e.Pointer.PointerId] = newViews;
        }
Beispiel #2
0
        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(originalSource))
            {
                return null;
            }

            // population of sources provided by:
            // http://stackoverflow.com/questions/2059475/what-is-the-silverlights-findelementsinhostcoordinates-equivalent-in-wpf
            var sources = new List<DependencyObject>();
            //changed from external edits, because VisualHit is
            //only a DependencyObject and may not be a UIElement
            //this could cause exceptions or may not be compiling at all
            //simply filter the result for class UIElement and
            //cast it to IEnumerable<UIElement> if you need
            //the very exact same result including type

            VisualTreeHelper.HitTest(
                _view,
                null,
                new HitTestResultCallback(
                    hit =>
                    {
                        var source = hit.VisualHit;
                        if (!source.HasTag())
                        {
                            return HitTestResultBehavior.Continue;
                        }
                        var pointerEvents = source.GetPointerEvents();
                        if (pointerEvents == PointerEvents.None || pointerEvents == PointerEvents.BoxNone)
                        {
                            return HitTestResultBehavior.Continue;
                        }

                        sources.Add(hit.VisualHit);
                        return HitTestResultBehavior.Continue;
                    }),
                new PointHitTestParameters(point));

            // Get the first React view that does not have pointer events set
            // to 'none' or 'box-none', and is not a child of a view with 
            // 'box-only' or 'none' settings for pointer events.

            // TODO: use pooled data structure
            var isBoxOnlyCache = new Dictionary<DependencyObject, bool>();
            foreach (var source in sources)
            {
                var viewHierarchy = RootViewHelper.GetReactViewHierarchy(source);
                var isBoxOnly = IsBoxOnlyWithCache(viewHierarchy, isBoxOnlyCache);
                if (!isBoxOnly)
                {
                    return (UIElement)source;
                }
            }

            return null;
        }
 private static bool ShouldSendPointerMoveEvent(DependencyObject view)
 {
     // This is a bubbling event, so we have to check if mouse move handler is hooked to any ancestor
     return(RootViewHelper.GetReactViewHierarchy(view).Any(
                v => v.GetMouseHandlerPresent(
                    ViewExtensions.MouseHandlerMask.MouseMove |
                    ViewExtensions.MouseHandlerMask.MouseMoveCapture)));
 }
Beispiel #4
0
        public int GetReactTagAtPoint(UIElement reactView, Point point)
        {
            var richTextBlock = reactView.As <TextBlock>();
            var textPointer   = richTextBlock.GetPositionFromPoint(point, true);
            var parentView    = RootViewHelper.GetReactViewHierarchy(textPointer.Parent).First();

            return(parentView.GetTag());
        }
        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(originalSource))
            {
                return(null);
            }

            var adjustedPoint = AdjustPointForStatusBar(point);
            var sources       = VisualTreeHelper.FindElementsInHostCoordinates(adjustedPoint, _view);

            // Get the first React view that does not have pointer events set
            // to 'none' or 'box-none', and is not a child of a view with
            // 'box-only' or 'none' settings for pointer events.

            // TODO: use pooled data structure
            var isBoxOnlyCache = new Dictionary <DependencyObject, bool>();

            foreach (var source in sources)
            {
                if (!source.HasTag())
                {
                    continue;
                }

                var pointerEvents = source.GetPointerEvents();
                if (pointerEvents == PointerEvents.None || pointerEvents == PointerEvents.BoxNone)
                {
                    continue;
                }

                var viewHierarchy = RootViewHelper.GetReactViewHierarchy(source);
                var isBoxOnly     = IsBoxOnlyWithCache(viewHierarchy, isBoxOnlyCache);
                if (!isBoxOnly)
                {
                    return(source);
                }
            }

            return(null);
        }
Beispiel #6
0
        private static bool ShouldSendEnterLeaveEvent(DependencyObject view)
        {
            // If the target is not a child of the root view, then this pointer
            // event does not belong to React.
            if (!RootViewHelper.IsReactSubview(view))
            {
                return false;
            }

            var viewHierarchy = RootViewHelper.GetReactViewHierarchy(view);
            foreach (var ancestor in viewHierarchy)
            {
                var pointerEvents = ancestor.GetPointerEvents();
                if (pointerEvents == PointerEvents.None || pointerEvents == PointerEvents.BoxNone)
                {
                    return false;
                }
            }

            return true;
        }
Beispiel #7
0
 public void RootViewHelper_Null()
 {
     Assert.IsNull(RootViewHelper.GetRootView(null));
 }
        private DependencyObject GetReactViewTarget(PointerRoutedEventArgs e)
        {
            // Ensure the original source is a DependencyObject
            var originalSource = e.OriginalSource as DependencyObject;

            if (originalSource == null)
            {
                return(null);
            }

            // Get the React view hierarchy from the original source.
            var enumerator = RootViewHelper.GetReactViewHierarchy(originalSource).GetEnumerator();

            // Try to get the first React view.
            if (!enumerator.MoveNext())
            {
                return(null);
            }

            // If the first React view has `pointerEvents: box-none`, revert
            // to using the `VisualTreeHelper` to find potentially occluded views.
            // This condition should be rare.
            if (enumerator.Current.GetPointerEvents() == PointerEvents.BoxNone)
            {
                var rootPoint     = e.GetCurrentPoint(_view);
                var transform     = _view.TransformToVisual(Window.Current.Content);
                var point         = transform.TransformPoint(rootPoint.Position);
                var adjustedPoint = AdjustPointForStatusBar(point);

                // Get the first view in at the pointer point that is not `box-none`.
                // Note: Rounding related errors can keep the position outside all react views, hence using FirstOrDefault.
                var nonBoxOnlyView = VisualTreeHelper.FindElementsInHostCoordinates(adjustedPoint, _view)
                                     .FirstOrDefault(v => v.HasTag() && v.GetPointerEvents() != PointerEvents.BoxNone);

                // Update the enumerator for the non-`box-only` view.
                enumerator = RootViewHelper.GetReactViewHierarchy(nonBoxOnlyView).GetEnumerator();
                if (!enumerator.MoveNext())
                {
                    return(null);
                }
            }

            // Views with `pointerEvents: none` are not hit test enabled, so we
            // will not encounter any view subtrees with this value. Given the
            // prior conditional for `pointerEvents: box-none`, we only need to
            // loop through the parents of the current React view target to
            // check for uses of `pointerEvents: box-only`. If found, the
            // parent becomes the new target.
            var source = enumerator.Current;

            while (enumerator.MoveNext())
            {
                var current = enumerator.Current;
                if (source == null || current.GetPointerEvents() == PointerEvents.BoxOnly)
                {
                    source = current;
                }
            }

            return(source);
        }
        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(originalSource))
            {
                return(null);
            }

            // population of sources provided by:
            // http://stackoverflow.com/questions/2059475/what-is-the-silverlights-findelementsinhostcoordinates-equivalent-in-wpf
            var sources = new List <DependencyObject>();

            //changed from external edits, because VisualHit is
            //only a DependencyObject and may not be a UIElement
            //this could cause exceptions or may not be compiling at all
            //simply filter the result for class UIElement and
            //cast it to IEnumerable<UIElement> if you need
            //the very exact same result including type

            VisualTreeHelper.HitTest(
                _view,
                null,
                new HitTestResultCallback(
                    hit =>
            {
                var source = hit.VisualHit;
                if (!source.HasTag())
                {
                    return(HitTestResultBehavior.Continue);
                }
                var pointerEvents = source.GetPointerEvents();
                if (pointerEvents == PointerEvents.None || pointerEvents == PointerEvents.BoxNone)
                {
                    return(HitTestResultBehavior.Continue);
                }

                sources.Add(hit.VisualHit);
                return(HitTestResultBehavior.Continue);
            }),
                new PointHitTestParameters(point));

            var viewHierarchy     = RootViewHelper.GetReactViewHierarchy(originalSource);
            var dependencyObjects = viewHierarchy as DependencyObject[] ?? viewHierarchy.ToArray();
            var enumerator        = dependencyObjects.GetEnumerator();

            if (!enumerator.MoveNext())
            {
                return(null);
            }

            if (enumerator.Current.GetPointerEvents() == PointerEvents.BoxNone)
            {
                var nonBoxOnlyView =
                    sources.FirstOrDefault(x => x.HasTag() && x.GetPointerEvents() != PointerEvents.BoxNone);

                enumerator = RootViewHelper.GetReactViewHierarchy(nonBoxOnlyView).GetEnumerator();
                if (!enumerator.MoveNext())
                {
                    return(null);
                }
            }

            var element = enumerator.Current;

            while (enumerator.MoveNext())
            {
                var current = enumerator.Current;
                if (element == null || current.GetPointerEvents() == PointerEvents.BoxOnly || !(element is UIElement))
                {
                    element = current;
                }
            }

            return((UIElement)element);
        }