From 8e5aca1746e148388fe1497681c0205dedf0d313 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 13 Nov 2023 19:50:15 -0500 Subject: [PATCH] fix: [iOS] fix pass-through element --- ...s.Flyout_OverlayInputPassThroughElement.cs | 6 +-- .../Xaml/Controls/Flyout/FlyoutPopupPanel.cs | 38 ++++++++++++++++--- src/Uno.UI/UI/Xaml/HitTestability.cs | 6 +-- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/FlyoutTests/UnoSamples_Tests.Flyout_OverlayInputPassThroughElement.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/FlyoutTests/UnoSamples_Tests.Flyout_OverlayInputPassThroughElement.cs index f29c6b86b195..e06d4d7efd27 100644 --- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/FlyoutTests/UnoSamples_Tests.Flyout_OverlayInputPassThroughElement.cs +++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Controls/FlyoutTests/UnoSamples_Tests.Flyout_OverlayInputPassThroughElement.cs @@ -57,9 +57,9 @@ public async Task FlyoutTest_When_NoOverlayInputPassThroughElement_Then_DontPass } #if !IS_RUNTIME_UI_TESTS - [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_DontPassThrough_withOn() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withOn"); - [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_DontPassThrough_withOff() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withOff"); - [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_DontPassThrough_withAuto() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withAuto"); + [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough_withOn() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withOn"); + [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough_withOff() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withOff"); + [Test][AutoRetry] public Task FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough_withAuto() => FlyoutTest_When_OverlayInputPassThroughElement_Then_PassThrough("withAuto"); #else [Test] [AutoRetry] diff --git a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutPopupPanel.cs b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutPopupPanel.cs index 79f80928b382..739b47ac50b9 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutPopupPanel.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Flyout/FlyoutPopupPanel.cs @@ -10,6 +10,8 @@ using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; +using Uno.Extensions; +using Uno.Foundation.Logging; using Uno.UI.Extensions; using Uno.UI.Xaml.Core; @@ -43,20 +45,42 @@ public FlyoutBasePopupPanel(FlyoutBase flyout) : base(flyout._popup) private protected override void OnPointerPressedDismissed(PointerRoutedEventArgs args) { - if (Flyout.OverlayInputPassThroughElement is not UIElement passThroughElement - || !Flyout.GetAllParents(includeCurrent: false).Contains(passThroughElement)) + if (this.Log().IsEnabled(LogLevel.Debug)) this.Log().Debug($"{this.GetDebugName()} Dismissing flyout (OverlayInputPassThroughElement:{Flyout.OverlayInputPassThroughElement.GetDebugIdentifier()})."); + + if (Flyout.OverlayInputPassThroughElement is not UIElement passThroughElement) + { + return; + } + + if (!Flyout.GetAllParents(includeCurrent: false).Contains(passThroughElement)) { // The element must be a parent of the Flyout (not 'this') to be able to receive the pointer events. + + if (this.Log().IsEnabled(LogLevel.Debug)) + this.Log().Debug($"{this.GetDebugName()} PassThroughElement ignored as element ({Flyout.OverlayInputPassThroughElement?.GetAllParents().Reverse().Select(elt => elt.GetDebugName()).JoinBy(">") ?? "--null--"})" + + $" is not a parent of the Flyout ({Flyout.GetAllParents().Select(elt => elt.GetDebugName()).Reverse().JoinBy(">")})."); + return; } var point = args.GetCurrentPoint(null); - var hitTestIgnoringThis = VisualTreeHelper.DefaultGetTestability.Except(this); + var hitTestIgnoringThis = VisualTreeHelper.DefaultGetTestability.Except(XamlRoot?.VisualTree.PopupRoot as UIElement ?? this); var (elementHitUnderOverlay, _) = VisualTreeHelper.HitTest(point.Position, passThroughElement.XamlRoot, hitTestIgnoringThis); - if (elementHitUnderOverlay is null - || !VisualTreeHelper.EnumerateAncestors(elementHitUnderOverlay).Contains(passThroughElement)) + if (elementHitUnderOverlay is null) { + if (this.Log().IsEnabled(LogLevel.Debug)) + this.Log().Debug($"{this.GetDebugName()} PassThroughElement ({passThroughElement.GetDebugName()}) ignored as hit-tested element is null."); + + return; + } + + if(!VisualTreeHelper.EnumerateAncestors(elementHitUnderOverlay).Contains(passThroughElement)) + { + if (this.Log().IsEnabled(LogLevel.Debug)) + this.Log().Debug($"{this.GetDebugName()} PassThroughElement ({passThroughElement.GetDebugName()}) ignored as hit-tested element ({elementHitUnderOverlay.GetDebugIdentifier()})" + + $" is not a child of the PassThroughElement ({VisualTreeHelper.EnumerateAncestors(elementHitUnderOverlay).Reverse().Select(elt => elt.GetDebugIdentifier()).JoinBy(">") ?? "--null--"})."); + // The element found by the HitTest is not a child of the pass-through element. return; } @@ -65,6 +89,10 @@ private protected override void OnPointerPressedDismissed(PointerRoutedEventArgs XamlRoot?.VisualTree.ContentRoot.InputManager.Pointers.ReRoute(args, from: this, to: elementHitUnderOverlay); #else XamlRoot?.VisualTree.RootVisual.ReRoutePointerDownEvent(args, from: this, to: elementHitUnderOverlay); +#if __IOS__ + // On iOS, the FlyoutPopupPanel does not have nay parent (cf. https://github.com/unoplatform/uno/issues/14405), so we are forcefully causing the ProcessPointerDown here. + XamlRoot?.VisualTree.RootVisual.ProcessPointerDown(args); +#endif #endif } } diff --git a/src/Uno.UI/UI/Xaml/HitTestability.cs b/src/Uno.UI/UI/Xaml/HitTestability.cs index 533cf0f9745d..c49ccb20585a 100644 --- a/src/Uno.UI/UI/Xaml/HitTestability.cs +++ b/src/Uno.UI/UI/Xaml/HitTestability.cs @@ -38,10 +38,10 @@ internal record struct StalePredicate(PredicateOfUIElement Method, string Name); internal static class GetHitTestabilityExtensions { /// - /// Wrap the given hitTest delegate to exclude (i.e. consider as ) the provided element. + /// Wrap the given hitTest delegate to exclude (i.e. consider as ) the provided element. /// /// The hit-testing delegate to wrap. - /// The element that should be considered as ) + /// The element that should be considered as ) /// internal static GetHitTestability Except(this GetHitTestability hitTest, UIElement element) { @@ -50,7 +50,7 @@ internal static GetHitTestability Except(this GetHitTestability hitTest, UIEleme { if (elt == element) { - return (HitTestability.Invisible, hitTest); + return (HitTestability.Collapsed, hitTest); } else {