internal static void ProcessPointerUp(PointerRoutedEventArgs args, bool isAfterHandledUp = false) { // We don't want handled events raised on RootVisual, // instead we wait for the element that handled it to directly forward it to us, // but only ** AFTER ** the up has been fully processed (with isAfterHandledUp = true). // This is required to be sure that element process gestures and manipulations before we raise the exit // (e.g. the 'tapped' event on a Button would be fired after the 'exit'). var isHandled = args.Handled; // Capture here as args might be reset before checked for focus var isUpFullyDispatched = isAfterHandledUp || !isHandled; if (!isUpFullyDispatched) { return; } #if __ANDROID__ || __WASM__ || __IOS__ #if __ANDROID__ || __IOS__ // Not needed on WASM as we do have native support of the exit event // On Android and iOS we use the RootVisual to raise the UWP only exit event (in managed only) if (args.Pointer.PointerDeviceType is PointerDeviceType.Touch && args.OriginalSource is UIElement src) { // It's acceptable to use only the OriginalSource on Android and iOS: // since those platforms have "implicit capture" and captures are propagated to the OS, // the OriginalSource will be the element that has capture (if any). src.RedispatchPointerExited(args.Reset(canBubbleNatively: false)); } #endif // Uno specific: To ensure focus is properly lost when clicking "outside" app's content, // we set focus here. In case UWP, focus is set to the root ScrollViewer instead, // but Uno does not have it on all targets yet. if (!isHandled && // so isAfterHandledUp is false! args.GetCurrentPoint(null).Properties.PointerUpdateKind is PointerUpdateKind.LeftButtonReleased && !PointerCapture.TryGet(args.Pointer, out _) && FocusManager.GetFocusedElement() is UIElement uiElement) { uiElement.Unfocus(); args.Handled = true; } ReleaseCaptures(args.Reset(canBubbleNatively: false)); #endif }
private bool OnNativeMotionEvent(MotionEvent nativeEvent, PointerRoutedEventArgs args, MotionEventActions action, bool isInView) { // Warning: MotionEvent of other kinds are filtered out in native code (UnoMotionHelper.java) switch (action) { case MotionEventActions.HoverEnter: return(OnNativePointerEnter(args)); case MotionEventActions.HoverExit when !args.Pointer.IsInContact: // When a mouse button is pressed or pen touches the screen (a.k.a. becomes in contact), we receive an HoverExit before the Down. // We validate here if pointer 'isInContact' (which is the case for HoverExit when mouse button pressed / pen touched the screen) // and we ignore them (as on UWP Exit is raised only when pointer moves out of bounds of the control, no matter the pressed state). // As a side effect we will have to update the hover state on each Move in order to handle the case of press -> move out -> release. return(OnNativePointerExited(args)); case MotionEventActions.HoverExit: return(false); // avoid useless logging case MotionEventActions.Down when args.Pointer.PointerDeviceType == PointerDeviceType.Touch: case MotionEventActions.PointerDown when args.Pointer.PointerDeviceType == PointerDeviceType.Touch: // We don't have any enter / exit on Android for touches, so we explicitly generate one on down / up. // That event args is requested to bubble in managed code only (args.CanBubbleNatively = false), // so we follow the same sequence as UWP (the whole tree gets entered before the pressed), // and we make sure that the event will bubble through the whole tree, no matter if the Pressed event is handle or not. // Note: Parents will also try to raise the "Enter" but they will be silent since the pointer is already considered as pressed. args.CanBubbleNatively = false; OnNativePointerEnter(args); return(OnNativePointerDown(args.Reset())); case PointerRoutedEventArgs.StylusWithBarrelDown: case MotionEventActions.Down: case MotionEventActions.PointerDown: return(OnNativePointerDown(args)); case MotionEventActions.Up when args.Pointer.PointerDeviceType == PointerDeviceType.Touch: case MotionEventActions.PointerUp when args.Pointer.PointerDeviceType == PointerDeviceType.Touch: // For touch pointer, in the RootVisual we will redispatch this event to raise exit, // but if the event has been handled, we need to raise it after the 'up' has been processed. if (OnNativePointerUp(args)) { Uno.UI.Xaml.Core.RootVisual.ProcessPointerUp(args, isAfterHandledUp: true); return(true); } else { return(false); } case PointerRoutedEventArgs.StylusWithBarrelUp: case MotionEventActions.Up: case MotionEventActions.PointerUp: return(OnNativePointerUp(args)); // We get ACTION_DOWN and ACTION_UP only for "left" button, and instead we get a HOVER_MOVE when pressing/releasing the right button of the mouse. // So on each POINTER_MOVE we make sure to update the pressed state if it does not match. // Note: We can also have HOVER_MOVE with barrel button pressed, so we make sure to "PointerDown" only for Mouse. case MotionEventActions.HoverMove when args.Pointer.PointerDeviceType == PointerDeviceType.Mouse && args.HasPressedButton && !IsPressed(args.Pointer): return(OnNativePointerDown(args) | OnNativePointerMoveWithOverCheck(args.Reset(), isInView)); case MotionEventActions.HoverMove when !args.HasPressedButton && IsPressed(args.Pointer): return(OnNativePointerUp(args) | OnNativePointerMoveWithOverCheck(args.Reset(), isInView)); case PointerRoutedEventArgs.StylusWithBarrelMove: case MotionEventActions.Move: case MotionEventActions.HoverMove: // Note: We use the OnNativePointerMove**WithOverCheck** in order to update the over state in case of press -> move out -> release // where Android won't raise the HoverExit (as it has raised it on press, but we have ignored it cf. HoverExit case.) return(OnNativePointerMoveWithOverCheck(args, isInView)); case MotionEventActions.Cancel: return(OnNativePointerCancel(args, isSwallowedBySystem: true)); default: if (this.Log().IsEnabled(LogLevel.Warning)) { this.Log().Warn($"We receive a native motion event of '{action}', but this is not supported and should have been filtered out in native code."); } return(false); } }
/// <summary> /// Used by the VisualRoot to redispatch a pointer exit on pointer up /// </summary> /// <param name="args"></param> internal void RedispatchPointerExited(PointerRoutedEventArgs args) => OnNativePointerExited(args.Reset(canBubbleNatively: false));
public override void TouchesBegan(NSSet touches, UIEvent evt) { if (!ArePointersEnabled) { return; // Will also prevent subsequents events } try { if (ManipulationMode == ManipulationModes.None) { // If manipulation mode is None, we make sure to disable scrollers directly on pointer pressed NotifyParentTouchesManagersManipulationStarted(); } var isHandledOrBubblingInManaged = default(bool); foreach (UITouch touch in touches) { var pt = TransientNativePointer.Get(this, touch); var args = new PointerRoutedEventArgs(pt.Id, touch, evt, this); // We set the DownArgs only for the top most element (a.k.a. OriginalSource) pt.DownArgs ??= args; if (pt.LastManagedOnlyFrameId >= args.FrameId) { continue; } // We don't have any enter on iOS for touches, so we explicitly generate one on down. // That event args is requested to bubble in managed code only (args.CanBubbleNatively = false), // so we follow the same sequence as UWP (the whole tree gets entered before the pressed), // and we make sure that the event will bubble through the whole tree, no matter if the Pressed event is handle or not. // Note: Parents will also try to raise the "Enter" but they will be silent since the pointer is already considered as pressed. args.CanBubbleNatively = false; OnNativePointerEnter(args); isHandledOrBubblingInManaged |= OnNativePointerDown(args.Reset()); if (isHandledOrBubblingInManaged) { pt.LastManagedOnlyFrameId = args.FrameId; } } /* * If we do not propagate the "TouchesBegan" to the parents (if isHandledOrBubblingInManaged), * they won't receive the "TouchesMoved" nor the "TouchesEnded". * * It means that if a control (like the Button) handles the "Pressed" (or the "Entered") * parent won't receive any touch event. * * To avoid that, we never prevent the base.TouchesBegan, but instead we keep track of the FrameId, * and then in parents control filter out events that was already raised in managed. */ // Continue native bubbling up of the event base.TouchesBegan(touches, evt); } catch (Exception e) { Application.Current.RaiseRecoverableUnhandledException(e); } }