Skip to content

Commit

Permalink
feat(pointers): Support pass through on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Apr 19, 2024
1 parent a409065 commit 11ad2a5
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 31 deletions.
16 changes: 16 additions & 0 deletions src/Uno.UI/Extensions/UIEventExtensions.iOSmacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ internal static bool IsTouchInView(this _Touch touch, _View view)
&& screenLocation.X < bounds.Right
&& screenLocation.Y < bounds.Bottom;
}

internal static UIElement FindOriginalSource(this _Touch touch)
{
var view = touch.View;
while (view != null)
{
if (view is UIElement elt)
{
return elt;
}

view = view.Superview;
}

return null;
}
#endif

/// <summary>
Expand Down
20 changes: 2 additions & 18 deletions src/Uno.UI/UI/Xaml/Input/PointerRoutedEventArgs.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal PointerRoutedEventArgs(PointerRoutedEventArgs previous, PointerRoutedEv
_properties = previous._properties;
}

internal PointerRoutedEventArgs(uint pointerId, UITouch nativeTouch, UIEvent nativeEvent, UIElement receiver) : this()
internal PointerRoutedEventArgs(uint pointerId, UITouch nativeTouch, UIEvent nativeEvent, UIElement originalSource) : this()
{
_nativeTouch = nativeTouch;
_nativeEvent = nativeEvent;
Expand All @@ -55,7 +55,7 @@ internal PointerRoutedEventArgs(uint pointerId, UITouch nativeTouch, UIEvent nat
FrameId = ToFrameId(_nativeTouch.Timestamp);
Pointer = new Pointer(pointerId, deviceType, isInContact, isInRange: true);
KeyModifiers = VirtualKeyModifiers.None;
OriginalSource = FindOriginalSource(_nativeTouch) ?? receiver;
OriginalSource = originalSource;

_properties = GetProperties(); // Make sure to capture the properties state so we can re-use them in "mixed" ctor
}
Expand Down Expand Up @@ -119,22 +119,6 @@ private static uint ToFrameId(double timestamp)
// We use modulo to make sure to reset to 0 in that case (1.13 years of app run-time, but we prefer to be safe).
return (uint)(frameId % uint.MaxValue);
}

private static UIElement FindOriginalSource(UITouch touch)
{
var view = touch.View;
while (view != null)
{
if (view is UIElement elt)
{
return elt;
}

view = view.Superview;
}

return null;
}
#endregion
}
}
5 changes: 5 additions & 0 deletions src/Uno.UI/UI/Xaml/Internal/InputManager.Pointers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ internal void ProcessPointerDown(PointerRoutedEventArgs args)

// Raise the event to the target
reRouted.To.OnPointerDown(args);
#if __IOS__
// Also as the FlyoutPopupPanel is being removed from the UI tree, we won't get any ProcessPointerUp, so we are forcefully causing it here.
args.Reset(canBubbleNatively: false);
reRouted.To.OnPointerUp(args);
#endif

args.Handled = true; // Make sure the event is flagged as handled so it won't be bubbled by native code to us again from the FlyoutPopupPanel.
return; // The event already came back to us (due to reRouted.To.OnPointerDown(args)).
Expand Down
79 changes: 66 additions & 13 deletions src/Uno.UI/UI/Xaml/UIElement.Pointers.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Foundation;
using UIKit;
using Uno.Extensions;
using Uno.Foundation.Logging;
using Uno.UI.Extensions;
using Uno.UI.Xaml.Core;
using WinUICoreServices = Uno.UI.Xaml.Core.CoreServices;
Expand Down Expand Up @@ -75,25 +76,39 @@ public void Release(UIElement element)
}
}

[ThreadStatic]
private static UIElement _sequenceReRouteTarget;
public static void ReRoutePointerSequenceTo(UIElement target)
=> _sequenceReRouteTarget = target;

private IEnumerable<TouchesManager> _parentsTouchesManager;
private bool _isManipulating;

partial void InitializePointersPartial()
{
MultipleTouchEnabled = true;

ArePointersEnabled = true;
}

#region Native touch handling (i.e. source of the pointer / gesture events)
public override void TouchesBegan(NSSet touches, UIEvent evt)
=> TouchesBegan(touches, evt, canBubbleNatively: true);
{
if (_sequenceReRouteTarget is { } target && target != this)
{
if (this.Log().IsEnabled(LogLevel.Debug))
this.Log().Debug($"Re-routing pointer sequence (implicit capture) from {this.GetDebugName()} to {target.GetDebugName()}");

target.TouchesBegan(touches, evt, canBubbleNatively: false, forcedOriginalSource: target);
}

TouchesBegan(touches, evt, canBubbleNatively: true);
}

/// <summary>
/// WARNING: canBubbleNatively=false on TouchesBegan has MAJOR impact regarding future events, use precautiously!
/// (cf. remarks in the method)
/// </summary>
internal void TouchesBegan(NSSet touches, UIEvent evt, bool canBubbleNatively)
internal void TouchesBegan(NSSet touches, UIEvent evt, bool canBubbleNatively, UIElement forcedOriginalSource = null)
{
#if TRACE_NATIVE_POINTER_EVENTS
Console.WriteLine($"{this.GetDebugIdentifier()} [TOUCHES_BEGAN] enabled:{ArePointersEnabled}");
Expand All @@ -116,7 +131,8 @@ internal void TouchesBegan(NSSet touches, UIEvent evt, bool canBubbleNatively)
foreach (UITouch touch in touches)
{
var pt = TransientNativePointer.Get(this, touch);
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, this) { CanBubbleNatively = canBubbleNatively };
var src = forcedOriginalSource ?? touch.FindOriginalSource() ?? this;
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, src) { CanBubbleNatively = canBubbleNatively };

// We set the DownArgs only for the top most element (a.k.a. OriginalSource)
pt.DownArgs ??= args;
Expand Down Expand Up @@ -166,9 +182,19 @@ internal void TouchesBegan(NSSet touches, UIEvent evt, bool canBubbleNatively)
}

public override void TouchesMoved(NSSet touches, UIEvent evt)
=> TouchesMoved(touches, evt, canBubbleNatively: true);
{
if (_sequenceReRouteTarget is { } target && target != this)
{
if (this.Log().IsEnabled(LogLevel.Debug))
this.Log().Debug($"Re-routing pointer sequence (implicit capture) from {this.GetDebugName()} to {target.GetDebugName()}");

target.TouchesMoved(touches, evt, canBubbleNatively: false, forcedOriginalSource: target);
}

TouchesMoved(touches, evt, canBubbleNatively: true);
}

internal void TouchesMoved(NSSet touches, UIEvent evt, bool canBubbleNatively)
internal void TouchesMoved(NSSet touches, UIEvent evt, bool canBubbleNatively, UIElement forcedOriginalSource = null)
{
#if TRACE_NATIVE_POINTER_EVENTS
Console.WriteLine($"{this.GetDebugIdentifier()} [TOUCHES_MOVED]");
Expand All @@ -180,7 +206,8 @@ internal void TouchesMoved(NSSet touches, UIEvent evt, bool canBubbleNatively)
foreach (UITouch touch in touches)
{
var pt = TransientNativePointer.Get(this, touch);
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, this) { CanBubbleNatively = canBubbleNatively };
var src = forcedOriginalSource ?? touch.FindOriginalSource() ?? this;
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, src) { CanBubbleNatively = canBubbleNatively };
var isPointerOver = touch.IsTouchInView(this);

// This is acceptable to keep that flag in a kind-of static way, since iOS do "implicit captures",
Expand All @@ -205,9 +232,21 @@ internal void TouchesMoved(NSSet touches, UIEvent evt, bool canBubbleNatively)
}

public override void TouchesEnded(NSSet touches, UIEvent evt)
=> TouchesEnded(touches, evt, canBubbleNatively: true);
{
if (_sequenceReRouteTarget is { } target && target != this)
{
_sequenceReRouteTarget = null;

if (this.Log().IsEnabled(LogLevel.Debug))
this.Log().Debug($"Re-routing pointer sequence (implicit capture) from {this.GetDebugName()} to {target.GetDebugName()}");

target.TouchesEnded(touches, evt, canBubbleNatively: false, forcedOriginalSource: target);
}

TouchesEnded(touches, evt, canBubbleNatively: true);
}

internal void TouchesEnded(NSSet touches, UIEvent evt, bool canBubbleNatively)
internal void TouchesEnded(NSSet touches, UIEvent evt, bool canBubbleNatively, UIElement forcedOriginalSource = null)
{
#if TRACE_NATIVE_POINTER_EVENTS
Console.WriteLine($"{this.GetDebugIdentifier()} [TOUCHES_ENDED]");
Expand Down Expand Up @@ -236,7 +275,8 @@ break lots of control (ScrollViewer) and ability to easily integrate an external
foreach (UITouch touch in touches)
{
var pt = TransientNativePointer.Get(this, touch);
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, this) { CanBubbleNatively = canBubbleNatively };
var src = forcedOriginalSource ?? touch.FindOriginalSource() ?? this;
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, src) { CanBubbleNatively = canBubbleNatively };

if (!pt.HadMove)
{
Expand Down Expand Up @@ -283,9 +323,21 @@ break lots of control (ScrollViewer) and ability to easily integrate an external
}

public override void TouchesCancelled(NSSet touches, UIEvent evt)
=> TouchesCancelled(touches, evt, canBubbleNatively: true);
{
if (_sequenceReRouteTarget is { } target && target != this)
{
_sequenceReRouteTarget = null;

if (this.Log().IsEnabled(LogLevel.Debug))
this.Log().Debug($"Re-routing pointer sequence (implicit capture) from {this.GetDebugName()} to {target.GetDebugName()}");

target.TouchesCancelled(touches, evt, canBubbleNatively: false, forcedOriginalSource: target);
}

TouchesCancelled(touches, evt, canBubbleNatively: true);
}

internal void TouchesCancelled(NSSet touches, UIEvent evt, bool canBubbleNatively)
internal void TouchesCancelled(NSSet touches, UIEvent evt, bool canBubbleNatively, UIElement forcedOriginalSource = null)
{
#if TRACE_NATIVE_POINTER_EVENTS
Console.WriteLine($"{this.GetDebugIdentifier()} [TOUCHES_CANCELLED]");
Expand All @@ -297,7 +349,8 @@ internal void TouchesCancelled(NSSet touches, UIEvent evt, bool canBubbleNativel
foreach (UITouch touch in touches)
{
var pt = TransientNativePointer.Get(this, touch);
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, this) { CanBubbleNatively = canBubbleNatively };
var src = forcedOriginalSource ?? touch.FindOriginalSource() ?? this;
var args = new PointerRoutedEventArgs(pt.Id, touch, evt, src) { CanBubbleNatively = canBubbleNatively };

// Note: We should have raise either PointerCaptureLost or PointerCancelled here depending of the reason which
// drives the system to bubble a lost. However we don't have this kind of information on iOS, and it's
Expand Down

0 comments on commit 11ad2a5

Please sign in to comment.