From 4c4a71ee505b1251a59a0384ae15544b8ed7cae6 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 5 Mar 2024 23:42:24 +0200 Subject: [PATCH 01/16] ContentView also needs to handle InputTransparent --- .../src/Handlers/Layout/LayoutHandler.Android.cs | 5 +---- src/Core/src/Handlers/View/ViewHandler.cs | 8 +++++--- src/Core/src/Platform/Android/ContentViewGroup.cs | 14 +++++++++++++- src/Core/src/Platform/Android/LayoutViewGroup.cs | 2 +- src/Core/src/Platform/Android/WrapperView.cs | 2 +- .../src/Platform/IInputTransparentManagingView.cs | 7 +++++++ .../PublicAPI/net-android/PublicAPI.Unshipped.txt | 1 + src/Core/src/ViewExtensions.cs | 4 ++-- 8 files changed, 31 insertions(+), 12 deletions(-) create mode 100644 src/Core/src/Platform/IInputTransparentManagingView.cs diff --git a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs index 1934477a27c7..5ade2127d43e 100644 --- a/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs +++ b/src/Core/src/Handlers/Layout/LayoutHandler.Android.cs @@ -155,10 +155,7 @@ public static partial void MapBackground(ILayoutHandler handler, ILayout layout) public static partial void MapInputTransparent(ILayoutHandler handler, ILayout layout) { - if (handler.PlatformView is LayoutViewGroup layoutViewGroup) - { - layoutViewGroup.InputTransparent = layout.InputTransparent; - } + ViewHandler.MapInputTransparent(handler, layout); } } } diff --git a/src/Core/src/Handlers/View/ViewHandler.cs b/src/Core/src/Handlers/View/ViewHandler.cs index eb3ac9b6eaaf..9165ae1d4a75 100644 --- a/src/Core/src/Handlers/View/ViewHandler.cs +++ b/src/Core/src/Handlers/View/ViewHandler.cs @@ -127,7 +127,7 @@ public bool HasContainer /// public virtual bool NeedsContainer { - get => VirtualView.NeedsContainer(); + get => VirtualView.NeedsContainer(PlatformView); } /// @@ -416,7 +416,7 @@ public static void MapContainerView(IViewHandler handler, IView view) if (handler is ViewHandler viewHandler) handler.HasContainer = viewHandler.NeedsContainer; else - handler.HasContainer = view.NeedsContainer(); + handler.HasContainer = view.NeedsContainer(handler.PlatformView as PlatformView); } /// @@ -482,7 +482,9 @@ public static void MapInputTransparent(IViewHandler handler, IView view) #if ANDROID handler.UpdateValue(nameof(IViewHandler.ContainerView)); - if (handler.ContainerView is WrapperView wrapper) + if (handler.PlatformView is IInputTransparentManagingView managing) + managing.InputTransparent = view.InputTransparent; + else if (handler.ContainerView is WrapperView wrapper) wrapper.InputTransparent = view.InputTransparent; #else diff --git a/src/Core/src/Platform/Android/ContentViewGroup.cs b/src/Core/src/Platform/Android/ContentViewGroup.cs index a7d1194d1058..bb212984a5f5 100644 --- a/src/Core/src/Platform/Android/ContentViewGroup.cs +++ b/src/Core/src/Platform/Android/ContentViewGroup.cs @@ -9,11 +9,13 @@ namespace Microsoft.Maui.Platform { - public class ContentViewGroup : PlatformContentViewGroup, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable + public class ContentViewGroup : PlatformContentViewGroup, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IInputTransparentManagingView { IBorderStroke? _clip; readonly Context _context; + bool IInputTransparentManagingView.InputTransparent { get; set; } + public ContentViewGroup(Context context) : base(context) { _context = context; @@ -99,6 +101,16 @@ protected override void OnLayout(bool changed, int left, int top, int right, int CrossPlatformArrange(destination); } + public override bool OnTouchEvent(MotionEvent? e) + { + if (((IInputTransparentManagingView)this).InputTransparent) + { + return false; + } + + return base.OnTouchEvent(e); + } + internal IBorderStroke? Clip { get => _clip; diff --git a/src/Core/src/Platform/Android/LayoutViewGroup.cs b/src/Core/src/Platform/Android/LayoutViewGroup.cs index 6b4f40dad1d2..e37286b1ee67 100644 --- a/src/Core/src/Platform/Android/LayoutViewGroup.cs +++ b/src/Core/src/Platform/Android/LayoutViewGroup.cs @@ -11,7 +11,7 @@ namespace Microsoft.Maui.Platform { - public class LayoutViewGroup : ViewGroup, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable + public class LayoutViewGroup : ViewGroup, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IInputTransparentManagingView { readonly ARect _clipRect = new(); readonly Context _context; diff --git a/src/Core/src/Platform/Android/WrapperView.cs b/src/Core/src/Platform/Android/WrapperView.cs index c15495bef315..52e5aac0d20d 100644 --- a/src/Core/src/Platform/Android/WrapperView.cs +++ b/src/Core/src/Platform/Android/WrapperView.cs @@ -10,7 +10,7 @@ namespace Microsoft.Maui.Platform { - public partial class WrapperView : PlatformWrapperView + public partial class WrapperView : PlatformWrapperView, IInputTransparentManagingView { const int MaximumRadius = 100; diff --git a/src/Core/src/Platform/IInputTransparentManagingView.cs b/src/Core/src/Platform/IInputTransparentManagingView.cs new file mode 100644 index 000000000000..c7e5968be219 --- /dev/null +++ b/src/Core/src/Platform/IInputTransparentManagingView.cs @@ -0,0 +1,7 @@ +namespace Microsoft.Maui.Platform +{ + internal interface IInputTransparentManagingView + { + bool InputTransparent { get; set; } + } +} \ No newline at end of file diff --git a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt index 0e4bd6077a93..36eb39bc2853 100644 --- a/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt @@ -65,6 +65,7 @@ override Microsoft.Maui.Layouts.FlexBasis.Equals(object? obj) -> bool override Microsoft.Maui.Layouts.FlexBasis.GetHashCode() -> int override Microsoft.Maui.MauiAppCompatActivity.DispatchTouchEvent(Android.Views.MotionEvent? e) -> bool override Microsoft.Maui.Platform.ContentViewGroup.GetClipPath(int width, int height) -> Android.Graphics.Path? +override Microsoft.Maui.Platform.ContentViewGroup.OnTouchEvent(Android.Views.MotionEvent? e) -> bool override Microsoft.Maui.Platform.MauiScrollView.OnMeasure(int widthMeasureSpec, int heightMeasureSpec) -> void override Microsoft.Maui.Platform.NavigationViewFragment.OnDestroy() -> void override Microsoft.Maui.PlatformContentViewGroup.JniPeerMembers.get -> Java.Interop.JniPeerMembers! diff --git a/src/Core/src/ViewExtensions.cs b/src/Core/src/ViewExtensions.cs index 75cc700f25f7..1652cf416335 100644 --- a/src/Core/src/ViewExtensions.cs +++ b/src/Core/src/ViewExtensions.cs @@ -49,13 +49,13 @@ public static partial class ViewExtensions #endif #if !TIZEN - internal static bool NeedsContainer(this IView? view) + internal static bool NeedsContainer(this IView? view, PlatformView? platformView) { if (view?.Clip != null || view?.Shadow != null) return true; #if ANDROID - if (view?.InputTransparent == true) + if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) return true; #endif From 2ab7b5a0a00809a922fee7ece637d60be4ed9bb3 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 00:31:15 +0200 Subject: [PATCH 02/16] ScrollView also needs to support InputTransparent --- .../src/Platform/Android/MauiScrollView.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Core/src/Platform/Android/MauiScrollView.cs b/src/Core/src/Platform/Android/MauiScrollView.cs index 0f182b7d22f6..c7281dc38092 100644 --- a/src/Core/src/Platform/Android/MauiScrollView.cs +++ b/src/Core/src/Platform/Android/MauiScrollView.cs @@ -11,7 +11,7 @@ namespace Microsoft.Maui.Platform { - public class MauiScrollView : NestedScrollView, IScrollBarView, NestedScrollView.IOnScrollChangeListener + public class MauiScrollView : NestedScrollView, IScrollBarView, NestedScrollView.IOnScrollChangeListener, IInputTransparentManagingView { View? _content; @@ -26,6 +26,8 @@ public class MauiScrollView : NestedScrollView, IScrollBarView, NestedScrollView internal float LastY { get; set; } internal bool ShouldSkipOnTouch; + + bool IInputTransparentManagingView.InputTransparent { get; set; } public MauiScrollView(Context context) : base(context) { @@ -131,11 +133,14 @@ public void SetOrientation(ScrollOrientation orientation) public override bool OnInterceptTouchEvent(MotionEvent? ev) { - // See also MauiHorizontalScrollView notes in OnInterceptTouchEvent - if (ev == null) return false; + if (((IInputTransparentManagingView)this).InputTransparent) + { + return false; + } + // set the start point for the bidirectional scroll; // Down is swallowed by other controls, so we'll just sneak this in here without actually preventing // other controls from getting the event. @@ -153,6 +158,11 @@ public override bool OnTouchEvent(MotionEvent? ev) if (ev == null || !Enabled || _scrollOrientation == ScrollOrientation.Neither) return false; + if (((IInputTransparentManagingView)this).InputTransparent) + { + return false; + } + if (ShouldSkipOnTouch) { ShouldSkipOnTouch = false; @@ -164,7 +174,7 @@ public override bool OnTouchEvent(MotionEvent? ev) // We'll fall through to the base event so we still get the fling from the ScrollViews. // We have to do this in both ScrollViews, since a single gesture will be owned by one or the other, depending // on the initial direction of movement (i.e., horizontal/vertical). - if (_isBidirectional) // // See also MauiHorizontalScrollView notes in OnInterceptTouchEvent + if (_isBidirectional) { float dX = LastX - ev.RawX; @@ -371,11 +381,6 @@ public override bool OnInterceptTouchEvent(MotionEvent? ev) if (ev == null || _parentScrollView == null) return false; - // TODO ezhart 2021-07-12 The previous version of this checked _renderer.Element.InputTransparent; we don't have acces to that here, - // and I'm not sure it even applies. We need to determine whether touch events will get here at all if we've marked the ScrollView InputTransparent - // We _should_ be able to deal with it at the handler level by force-setting an OnTouchListener for the PlatformView that always returns false; then we - // can just stop worrying about it here because the touches _can't_ reach this. - // set the start point for the bidirectional scroll; // Down is swallowed by other controls, so we'll just sneak this in here without actually preventing // other controls from getting the event. From bbdc4594d9670285b5dfa27e566d11365a390572 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 01:15:52 +0200 Subject: [PATCH 03/16] Add some tests --- .../Concepts/InputTransparencyGalleryPage.cs | 76 +++++++++++++++++++ .../samples/Controls.Sample.UITests/Test.cs | 16 ++++ .../Concepts/InputTransparencyGalleryTests.cs | 20 +++++ src/Core/src/Platform/Tizen/ViewExtensions.cs | 8 -- src/Core/src/ViewExtensions.cs | 13 ++-- 5 files changed, 120 insertions(+), 13 deletions(-) diff --git a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs index 98be75b6debc..be27edf5ef5e 100644 --- a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs +++ b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs @@ -1,5 +1,6 @@ using System; using Microsoft.Maui.Controls; +using Microsoft.Maui; namespace Maui.Controls.Sample { @@ -147,8 +148,83 @@ protected override void Build() AddNesting(rt, rc, nt, nc, t, clickable, passthru); } + + // Tests for a content view (content view, button) with some variations to ensure + // that all combinations are correctly clickable + foreach (var state in Test.InputTransparencyMatrix.SimpleStates) + { + var (rt, rc, t) = state.Key; + var (clickable, passthru) = state.Value; + + AddSimpleNesting(rt, rc, t, clickable, passthru); + } + + // Tests for a scroll view (scroll view, button) with some variations to ensure + // that all combinations are correctly clickable + foreach (var state in Test.InputTransparencyMatrix.SimpleStates) + { + var (rt, rc, t) = state.Key; + var (clickable, passthru) = state.Value; + + AddSimpleNesting(rt, rc, t, clickable, passthru); + } } + void AddSimpleNesting(bool rootTrans, bool rootCascade, bool trans, bool isClickable, bool isPassThru) where T : Microsoft.Maui.Controls.Compatibility.Layout, IContentView, new() => + Add(Test.InputTransparencyMatrix.GetSimpleKey(typeof(T).Name, rootTrans, rootCascade, trans, isClickable, isPassThru), () => + { + var bottom = new Button { Text = "Bottom Button" }; + var top = new Button + { + InputTransparent = trans, + Text = "Click Me!" + }; + var root = new T + { + InputTransparent = rootTrans, + CascadeInputTransparent = rootCascade, + }; + root.GetType().GetProperty("Content").SetValue(root, top); + var grid = new Grid + { + new Grid { bottom }, + root + }; + return (grid, new { Bottom = bottom, Top = top }); + }) + .With(t => + { + var v = t.ViewContainer.View; + var bottom = t.Additional.Bottom; + var top = Annotate(t.Additional.Top, v); + if (isClickable) + { + // if the button is clickable, then it should be clickable + bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + top.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); + } + else if (!isPassThru) + { + // if one of the parent layouts are NOT transparent, then + // the tap should NOT go through to the bottom button +#if ANDROID + // TODO: Android is broken with everything passing through + // https://github.com/dotnet/maui/issues/10252 + bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); +#else + bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); +#endif + } + else + { + // otherwise, the tap should go through + bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + } + }); + void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedCascade, bool trans, bool isClickable, bool isPassThru) => Add(Test.InputTransparencyMatrix.GetKey(rootTrans, rootCascade, nestedTrans, nestedCascade, trans, isClickable, isPassThru), () => { diff --git a/src/Controls/samples/Controls.Sample.UITests/Test.cs b/src/Controls/samples/Controls.Sample.UITests/Test.cs index b5b94adc4ff6..3426230216be 100644 --- a/src/Controls/samples/Controls.Sample.UITests/Test.cs +++ b/src/Controls/samples/Controls.Sample.UITests/Test.cs @@ -773,8 +773,24 @@ public static class InputTransparencyMatrix [(false, false, false, false, false)] = (truee, false), }; + public static readonly IReadOnlyDictionary<(bool RT, bool RC, bool T), (bool Clickable, bool PassThru)> SimpleStates = + new Dictionary<(bool, bool, bool), (bool, bool)> + { + [(truee, truee, truee)] = (false, truee), + [(truee, truee, false)] = (false, truee), + [(truee, false, truee)] = (false, truee), + [(truee, false, false)] = (truee, false), + [(false, truee, truee)] = (false, false), + [(false, truee, false)] = (truee, false), + [(false, false, truee)] = (false, false), + [(false, false, false)] = (truee, false), + }; + public static string GetKey(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedCascade, bool trans, bool isClickable, bool isPassThru) => $"Root{(rootTrans ? "Trans" : "")}{(rootCascade ? "Cascade" : "")}Nested{(nestedTrans ? "Trans" : "")}{(nestedCascade ? "Cascade" : "")}Control{(trans ? "Trans" : "")}Is{(isClickable ? "" : "Not")}ClickableIs{(isPassThru ? "" : "Not")}PassThru"; + + public static string GetSimpleKey(string prefix, bool rootTrans, bool rootCascade, bool trans, bool isClickable, bool isPassThru) => + $"{prefix}{(rootTrans ? "Trans" : "")}{(rootCascade ? "Cascade" : "")}Control{(trans ? "Trans" : "")}Is{(isClickable ? "" : "Not")}ClickableIs{(isPassThru ? "" : "Not")}PassThru"; } } } \ No newline at end of file diff --git a/src/Controls/tests/UITests/Tests/Concepts/InputTransparencyGalleryTests.cs b/src/Controls/tests/UITests/Tests/Concepts/InputTransparencyGalleryTests.cs index 8e69b47a61d2..54be15fb8bf3 100644 --- a/src/Controls/tests/UITests/Tests/Concepts/InputTransparencyGalleryTests.cs +++ b/src/Controls/tests/UITests/Tests/Concepts/InputTransparencyGalleryTests.cs @@ -42,6 +42,26 @@ public void InputTransparencyWhenRootIsNotTransparentMatrix([Values] bool rootCa RunTest(key, clickable, passthru); } + [Test] + [Combinatorial] + public void ScrollViewInputTransparencySimpleMatrix([Values] bool rootTrans, [Values] bool rootCascade, [Values] bool trans) + { + var (clickable, passthru) = Test.InputTransparencyMatrix.SimpleStates[(rootTrans, rootCascade, trans)]; + var key = Test.InputTransparencyMatrix.GetSimpleKey("ScrollView", rootTrans, rootCascade, trans, clickable, passthru); + + RunTest(key, clickable, passthru); + } + + [Test] + [Combinatorial] + public void ContentViewInputTransparencySimpleMatrix([Values] bool rootTrans, [Values] bool rootCascade, [Values] bool trans) + { + var (clickable, passthru) = Test.InputTransparencyMatrix.SimpleStates[(rootTrans, rootCascade, trans)]; + var key = Test.InputTransparencyMatrix.GetSimpleKey("ContentView", rootTrans, rootCascade, trans, clickable, passthru); + + RunTest(key, clickable, passthru); + } + void RunTest(string test, bool? clickable = null, bool? passthru = null) { var remote = new EventViewContainerRemote(UITestContext, test); diff --git a/src/Core/src/Platform/Tizen/ViewExtensions.cs b/src/Core/src/Platform/Tizen/ViewExtensions.cs index 83e917292318..b5ca1d6dd39d 100644 --- a/src/Core/src/Platform/Tizen/ViewExtensions.cs +++ b/src/Core/src/Platform/Tizen/ViewExtensions.cs @@ -333,14 +333,6 @@ internal static IDisposable OnUnloaded(this NView view, Action action) return disposable; } - internal static bool NeedsContainer(this IView? view) - { - if (view is IBorderView border) - return border?.Shape != null || border?.Stroke != null; - - return false; - } - internal static T? GetChildAt(this NView view, int index) where T : NView { return (T?)view.Children[index]; diff --git a/src/Core/src/ViewExtensions.cs b/src/Core/src/ViewExtensions.cs index 1652cf416335..70e5c4c98b92 100644 --- a/src/Core/src/ViewExtensions.cs +++ b/src/Core/src/ViewExtensions.cs @@ -48,27 +48,30 @@ public static partial class ViewExtensions await Screenshot.Default.CaptureAsync(window); #endif -#if !TIZEN internal static bool NeedsContainer(this IView? view, PlatformView? platformView) { +#if !TIZEN if (view?.Clip != null || view?.Shadow != null) return true; +#endif #if ANDROID - if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) + if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) return true; #endif #if ANDROID || IOS if (view is IBorder border && border.Border != null) return true; -#elif WINDOWS - if (view is IBorderView border) +#endif + +#if WINDOWS || TIZEN + if (view is IBorderView border) return border?.Shape != null || border?.Stroke != null; #endif + return false; } -#endif } } From eae9698de7b2ba9490dd840dbf64e0bd28bcff75 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 01:30:28 +0200 Subject: [PATCH 04/16] this --- .../Concepts/InputTransparencyGalleryPage.cs | 92 ++++++------------- 1 file changed, 30 insertions(+), 62 deletions(-) diff --git a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs index be27edf5ef5e..03308746a77e 100644 --- a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs +++ b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs @@ -192,38 +192,7 @@ protected override void Build() }; return (grid, new { Bottom = bottom, Top = top }); }) - .With(t => - { - var v = t.ViewContainer.View; - var bottom = t.Additional.Bottom; - var top = Annotate(t.Additional.Top, v); - if (isClickable) - { - // if the button is clickable, then it should be clickable - bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - } - else if (!isPassThru) - { - // if one of the parent layouts are NOT transparent, then - // the tap should NOT go through to the bottom button -#if ANDROID - // TODO: Android is broken with everything passing through - // https://github.com/dotnet/maui/issues/10252 - bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); -#else - bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); -#endif - } - else - { - // otherwise, the tap should go through - bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - } - }); + .With(t => WithAssert(isClickable, isPassThru, t.ViewContainer, t.Additional.Bottom, Annotate(t.Additional.Top, t.ViewContainer.View))); void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedCascade, bool trans, bool isClickable, bool isPassThru) => Add(Test.InputTransparencyMatrix.GetKey(rootTrans, rootCascade, nestedTrans, nestedCascade, trans, isClickable, isPassThru), () => @@ -254,40 +223,39 @@ void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedC }; return (grid, new { Bottom = bottom, Top = top }); }) - .With(t => - { - var v = t.ViewContainer.View; - var bottom = t.Additional.Bottom; - var top = Annotate(t.Additional.Top, v); - if (isClickable) - { - // if the button is clickable, then it should be clickable - bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - } - else if (!isPassThru) - { - // if one of the parent layouts are NOT transparent, then - // the tap should NOT go through to the bottom button + .With(t => WithAssert(isClickable, isPassThru, t.ViewContainer, t.Additional.Bottom, Annotate(t.Additional.Top, t.ViewContainer.View))); + + private static void WithAssert(bool isClickable, bool isPassThru, ExpectedEventViewContainer viewContainer, Button bottom, Button top) + { + if (isClickable) + { + // if the button is clickable, then it should be clickable + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + } + else if (!isPassThru) + { + // if one of the parent layouts are NOT transparent, then + // the tap should NOT go through to the bottom button #if ANDROID - // TODO: Android is broken with everything passing through - // https://github.com/dotnet/maui/issues/10252 - bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + // TODO: Android is broken with everything passing through + // https://github.com/dotnet/maui/issues/10252 + bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); #else - bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); #endif - } - else - { - // otherwise, the tap should go through - bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); - } - }); + } + else + { + // otherwise, the tap should go through + bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + } + } - (ExpectedEventViewContainer ViewContainer, T Additional) Add(Test.InputTransparency test, Func<(View View, T Additional)> func) => + (ExpectedEventViewContainer ViewContainer, T Additional) Add(Test.InputTransparency test, Func<(View View, T Additional)> func) => Add(test.ToString(), func); (ExpectedEventViewContainer ViewContainer, T Additional) Add(string test, Func<(View View, T Additional)> func) From 1b03b629e088f779a5c45782ff5e70fbebaffa8a Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 01:31:46 +0200 Subject: [PATCH 05/16] hmm --- .../Concepts/InputTransparencyGalleryPage.cs | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs index 03308746a77e..fb81a2486ad8 100644 --- a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs +++ b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs @@ -225,37 +225,37 @@ void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedC }) .With(t => WithAssert(isClickable, isPassThru, t.ViewContainer, t.Additional.Bottom, Annotate(t.Additional.Top, t.ViewContainer.View))); - private static void WithAssert(bool isClickable, bool isPassThru, ExpectedEventViewContainer viewContainer, Button bottom, Button top) - { - if (isClickable) - { - // if the button is clickable, then it should be clickable - bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); - top.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); - } - else if (!isPassThru) - { - // if one of the parent layouts are NOT transparent, then - // the tap should NOT go through to the bottom button + private static void WithAssert(bool isClickable, bool isPassThru, ExpectedEventViewContainer viewContainer, Button bottom, Button top) + { + if (isClickable) + { + // if the button is clickable, then it should be clickable + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + } + else if (!isPassThru) + { + // if one of the parent layouts are NOT transparent, then + // the tap should NOT go through to the bottom button #if ANDROID // TODO: Android is broken with everything passing through // https://github.com/dotnet/maui/issues/10252 - bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent(); + bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); #else - bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); - top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); #endif - } - else - { - // otherwise, the tap should go through - bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => viewContainer.ReportFailEvent(); - } - } + } + else + { + // otherwise, the tap should go through + bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + } + } - (ExpectedEventViewContainer ViewContainer, T Additional) Add(Test.InputTransparency test, Func<(View View, T Additional)> func) => + (ExpectedEventViewContainer ViewContainer, T Additional) Add(Test.InputTransparency test, Func<(View View, T Additional)> func) => Add(test.ToString(), func); (ExpectedEventViewContainer ViewContainer, T Additional) Add(string test, Func<(View View, T Additional)> func) From e4d3e4243f9a125ab5c047e071add039a22e15e0 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 01:32:28 +0200 Subject: [PATCH 06/16] ws --- .../Concepts/InputTransparencyGalleryPage.cs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs index fb81a2486ad8..c18cfda99529 100644 --- a/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs +++ b/src/Controls/samples/Controls.Sample.UITests/Concepts/InputTransparencyGalleryPage.cs @@ -227,32 +227,32 @@ void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedC private static void WithAssert(bool isClickable, bool isPassThru, ExpectedEventViewContainer viewContainer, Button bottom, Button top) { - if (isClickable) - { - // if the button is clickable, then it should be clickable - bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); - top.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); - } - else if (!isPassThru) - { - // if one of the parent layouts are NOT transparent, then - // the tap should NOT go through to the bottom button + if (isClickable) + { + // if the button is clickable, then it should be clickable + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + } + else if (!isPassThru) + { + // if one of the parent layouts are NOT transparent, then + // the tap should NOT go through to the bottom button #if ANDROID - // TODO: Android is broken with everything passing through - // https://github.com/dotnet/maui/issues/10252 - bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + // TODO: Android is broken with everything passing through + // https://github.com/dotnet/maui/issues/10252 + bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); #else - bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); - top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + bottom.Clicked += (s, e) => viewContainer.ReportFailEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); #endif - } - else - { - // otherwise, the tap should go through - bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); - top.Clicked += (s, e) => viewContainer.ReportFailEvent(); - } + } + else + { + // otherwise, the tap should go through + bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent(); + top.Clicked += (s, e) => viewContainer.ReportFailEvent(); + } } (ExpectedEventViewContainer ViewContainer, T Additional) Add(Test.InputTransparency test, Func<(View View, T Additional)> func) => From 0c4289916a62755480e5aadc558b7842ae758aca Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 6 Mar 2024 01:35:17 +0200 Subject: [PATCH 07/16] ws --- src/Core/src/ViewExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/src/ViewExtensions.cs b/src/Core/src/ViewExtensions.cs index 70e5c4c98b92..7418ce97ed5a 100644 --- a/src/Core/src/ViewExtensions.cs +++ b/src/Core/src/ViewExtensions.cs @@ -56,7 +56,7 @@ internal static bool NeedsContainer(this IView? view, PlatformView? platformView #endif #if ANDROID - if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) + if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) return true; #endif @@ -65,8 +65,8 @@ internal static bool NeedsContainer(this IView? view, PlatformView? platformView return true; #endif -#if WINDOWS || TIZEN - if (view is IBorderView border) +#if WINDOWS || TIZEN + if (view is IBorderView border) return border?.Shape != null || border?.Stroke != null; #endif From 3defdf360b3eba98898f549d785b6ee10ade38e3 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 18 Jun 2024 21:11:15 +0800 Subject: [PATCH 08/16] fix some tests --- src/Core/src/ViewExtensions.cs | 2 +- .../HandlerTests/HandlerTestBaseOfT.Android.cs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Core/src/ViewExtensions.cs b/src/Core/src/ViewExtensions.cs index 7418ce97ed5a..19bc9747096e 100644 --- a/src/Core/src/ViewExtensions.cs +++ b/src/Core/src/ViewExtensions.cs @@ -56,7 +56,7 @@ internal static bool NeedsContainer(this IView? view, PlatformView? platformView #endif #if ANDROID - if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView)) + if (view?.InputTransparent == true && platformView is not IInputTransparentManagingView) return true; #endif diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs index 952ec19ac0de..4ca64f60f45a 100644 --- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs +++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBaseOfT.Android.cs @@ -164,8 +164,13 @@ public async Task NeedsContainerWhenInputTransparent() var handler = await CreateHandlerAsync(view); - if (handler is ViewHandler vh) - Assert.True(vh.NeedsContainer); + if (handler is not ViewHandler vh) + return; + + if (handler.PlatformView is IInputTransparentManagingView) + Assert.False(vh.NeedsContainer, $"{view.GetType().Name} should NOT need a container because it uses a IInputTransparentManagingView platform view."); + else + Assert.True(vh.NeedsContainer, $"{view.GetType().Name} SHOULD need a container because it does NOT use a IInputTransparentManagingView platform view."); } } } \ No newline at end of file From 269475e49b5b52b2e8e2cf0918cc2e808f79b94f Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 18 Jun 2024 23:18:02 +0800 Subject: [PATCH 09/16] This --- .../Tests/Concepts/InputTransparencyGalleryTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs index 784b617d12d5..827546201408 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs @@ -49,7 +49,8 @@ public void ScrollViewInputTransparencySimpleMatrix([Values] bool rootTrans, [Va var (clickable, passthru) = Test.InputTransparencyMatrix.SimpleStates[(rootTrans, rootCascade, trans)]; var key = Test.InputTransparencyMatrix.GetSimpleKey("ScrollView", rootTrans, rootCascade, trans, clickable, passthru); - RunTest(key, clickable, passthru); + // ScrollView is not really a layout so the "broken" rules don't apply + RunTest(key, clickable, passthru, androidIsBroken: false); } [Test] @@ -62,7 +63,7 @@ public void ContentViewInputTransparencySimpleMatrix([Values] bool rootTrans, [V RunTest(key, clickable, passthru); } - void RunTest(string test, bool? clickable = null, bool? passthru = null) + void RunTest(string test, bool? clickable = null, bool? passthru = null, bool androidIsBroken = true) { var remote = new EventViewContainerRemote(UITestContext, test); remote.GoTo(test.ToString()); @@ -84,7 +85,7 @@ void RunTest(string test, bool? clickable = null, bool? passthru = null) // if the button is clickable or taps pass through to the base button ClassicAssert.AreEqual($"Event: {test} (SUCCESS 1)", textAfterClick); } - else if (Device == TestDevice.Android) + else if (androidIsBroken && Device == TestDevice.Android) { // TODO: Android is broken with everything passing through so we just use that // to test the bottom button was clickable From 0b5a0b5eb3beabb4419a519e54e6a1b12f5fc9b5 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 19 Jun 2024 02:42:50 +0800 Subject: [PATCH 10/16] And iOS --- .../Elements/Layout/LayoutTests.iOS.cs | 2 +- .../IUserInteractionEnabledManagingView.cs | 6 +++ src/Core/src/Platform/iOS/LayoutView.cs | 48 ------------------ src/Core/src/Platform/iOS/MauiScrollView.cs | 50 ++++++++++++++++++- src/Core/src/Platform/iOS/MauiView.cs | 49 +++++++++++++++++- .../PublicAPI/net-ios/PublicAPI.Unshipped.txt | 11 +++- .../net-maccatalyst/PublicAPI.Unshipped.txt | 9 ++++ 7 files changed, 122 insertions(+), 53 deletions(-) create mode 100644 src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs diff --git a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs index 0069df6c74d7..9bda2f08deb8 100644 --- a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs @@ -160,7 +160,7 @@ void ValidateInputTransparentOnPlatformView(IView view) var platformView = view.ToPlatform(MauiContext); bool value = platformView.UserInteractionEnabled; - if (platformView is LayoutView lv) + if (platformView is IUserInteractionEnabledManagingView lv) value = lv.UserInteractionEnabledOverride; Assert.True(view.InputTransparent == !value, diff --git a/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs b/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs new file mode 100644 index 000000000000..54b540224b9f --- /dev/null +++ b/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs @@ -0,0 +1,6 @@ +namespace Microsoft.Maui.Platform; + +interface IUserInteractionEnabledManagingView +{ + bool UserInteractionEnabledOverride { get; } +} diff --git a/src/Core/src/Platform/iOS/LayoutView.cs b/src/Core/src/Platform/iOS/LayoutView.cs index 9c8aa866485a..3662681dbd6a 100644 --- a/src/Core/src/Platform/iOS/LayoutView.cs +++ b/src/Core/src/Platform/iOS/LayoutView.cs @@ -1,12 +1,9 @@ -using CoreGraphics; using UIKit; namespace Microsoft.Maui.Platform { public class LayoutView : MauiView { - bool _userInteractionEnabled; - public override void SubviewAdded(UIView uiview) { InvalidateConstraintsCache(); @@ -20,50 +17,5 @@ public override void WillRemoveSubview(UIView uiview) base.WillRemoveSubview(uiview); Superview?.SetNeedsLayout(); } - - public override UIView? HitTest(CGPoint point, UIEvent? uievent) - { - var result = base.HitTest(point, uievent); - - if (result is null) - { - return null; - } - - if (!_userInteractionEnabled && Equals(result)) - { - // If user interaction is disabled (IOW, if the corresponding Layout is InputTransparent), - // then we exclude the LayoutView itself from hit testing. But it's children are valid - // hit testing targets. - - return null; - } - - if (result is LayoutView layoutView && !layoutView.UserInteractionEnabledOverride) - { - // If the child is a layout then we need to check the UserInteractionEnabledOverride - // since layouts always have user interaction enabled. - - return null; - } - - return result; - } - - internal bool UserInteractionEnabledOverride => _userInteractionEnabled; - - public override bool UserInteractionEnabled - { - get => base.UserInteractionEnabled; - set - { - // We leave the base UIE value true no matter what, so that hit testing will find children - // of the LayoutView. But we track the intended value so we can use it during hit testing - // to ignore the LayoutView itself, if necessary. - - base.UserInteractionEnabled = true; - _userInteractionEnabled = value; - } - } } } \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/MauiScrollView.cs b/src/Core/src/Platform/iOS/MauiScrollView.cs index 00c1ca3d55e8..829087388aa8 100644 --- a/src/Core/src/Platform/iOS/MauiScrollView.cs +++ b/src/Core/src/Platform/iOS/MauiScrollView.cs @@ -5,8 +5,10 @@ namespace Microsoft.Maui.Platform { - public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents + public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents, IUserInteractionEnabledManagingView { + bool _userInteractionEnabledOverride; + public MauiScrollView() { } @@ -32,6 +34,50 @@ public override void MovedToWindow() base.MovedToWindow(); _movedToWindow?.Invoke(this, EventArgs.Empty); } + + public override UIView? HitTest(CGPoint point, UIEvent? uievent) + { + var result = base.HitTest(point, uievent); + + if (result is null) + { + return null; + } + + if (!_userInteractionEnabledOverride && Equals(result)) + { + // If user interaction is disabled (IOW, if the corresponding View is InputTransparent), + // then we exclude the managing view itself from hit testing. But it's children are valid + // hit testing targets. + + return null; + } + + if (result is IUserInteractionEnabledManagingView v && !v.UserInteractionEnabledOverride) + { + // If the child is a managing view then we need to check the UserInteractionEnabledOverride + // since managing view instances always have user interaction enabled. + + return null; + } + + return result; + } + + bool IUserInteractionEnabledManagingView.UserInteractionEnabledOverride => _userInteractionEnabledOverride; + + public override bool UserInteractionEnabled + { + get => base.UserInteractionEnabled; + set + { + // We leave the base UIE value true no matter what, so that hit testing will find children + // of the LayoutView. But we track the intended value so we can use it during hit testing + // to ignore the LayoutView itself, if necessary. + + base.UserInteractionEnabled = true; + _userInteractionEnabledOverride = value; + } + } } } - diff --git a/src/Core/src/Platform/iOS/MauiView.cs b/src/Core/src/Platform/iOS/MauiView.cs index e125a46f5586..ff88e3d1e0f3 100644 --- a/src/Core/src/Platform/iOS/MauiView.cs +++ b/src/Core/src/Platform/iOS/MauiView.cs @@ -7,7 +7,7 @@ namespace Microsoft.Maui.Platform { - public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IUIViewLifeCycleEvents + public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IUIViewLifeCycleEvents, IUserInteractionEnabledManagingView { static bool? _respondsToSafeArea; @@ -17,6 +17,8 @@ public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTre WeakReference? _reference; WeakReference? _crossPlatformLayoutReference; + bool _userInteractionEnabledOverride; + public IView? View { get => _reference != null && _reference.TryGetTarget(out var v) ? v : null; @@ -174,5 +176,50 @@ public override void MovedToWindow() base.MovedToWindow(); _movedToWindow?.Invoke(this, EventArgs.Empty); } + + public override UIView? HitTest(CGPoint point, UIEvent? uievent) + { + var result = base.HitTest(point, uievent); + + if (result is null) + { + return null; + } + + if (!_userInteractionEnabledOverride && Equals(result)) + { + // If user interaction is disabled (IOW, if the corresponding View is InputTransparent), + // then we exclude the managing view itself from hit testing. But it's children are valid + // hit testing targets. + + return null; + } + + if (result is IUserInteractionEnabledManagingView v && !v.UserInteractionEnabledOverride) + { + // If the child is a managing view then we need to check the UserInteractionEnabledOverride + // since managing view instances always have user interaction enabled. + + return null; + } + + return result; + } + + bool IUserInteractionEnabledManagingView.UserInteractionEnabledOverride => _userInteractionEnabledOverride; + + public override bool UserInteractionEnabled + { + get => base.UserInteractionEnabled; + set + { + // We leave the base UIE value true no matter what, so that hit testing will find children + // of the LayoutView. But we track the intended value so we can use it during hit testing + // to ignore the LayoutView itself, if necessary. + + base.UserInteractionEnabled = true; + _userInteractionEnabledOverride = value; + } + } } } diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index 1152b0f568a1..c64aeb8debc8 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -161,4 +161,13 @@ Microsoft.Maui.Platform.MauiView.IsMeasureValid(double widthConstraint, double h Microsoft.Maui.Platform.UIEdgeInsetsExtensions static Microsoft.Maui.Platform.UIEdgeInsetsExtensions.ToThickness(this UIKit.UIEdgeInsets insets) -> Microsoft.Maui.Thickness override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Graphics.Rect rect) -> void -*REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> void \ No newline at end of file +*REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> void +*REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.get -> bool +*REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.set -> void +*REMOVED*override Microsoft.Maui.Platform.LayoutView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? +override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.get -> bool +override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.set -> void +override Microsoft.Maui.Platform.MauiView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? +override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.get -> bool +override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.set -> void +override Microsoft.Maui.Platform.MauiScrollView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index 827532828734..b433d76b27f4 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -162,3 +162,12 @@ virtual Microsoft.Maui.MauiUIApplicationDelegate.PerformFetch(UIKit.UIApplicatio Microsoft.Maui.Platform.UIEdgeInsetsExtensions static Microsoft.Maui.Platform.UIEdgeInsetsExtensions.ToThickness(this UIKit.UIEdgeInsets insets) -> Microsoft.Maui.Thickness *REMOVED*override Microsoft.Maui.Platform.MauiLabel.InvalidateIntrinsicContentSize() -> void +*REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.get -> bool +*REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.set -> void +*REMOVED*override Microsoft.Maui.Platform.LayoutView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? +override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.get -> bool +override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.set -> void +override Microsoft.Maui.Platform.MauiView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? +override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.get -> bool +override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.set -> void +override Microsoft.Maui.Platform.MauiScrollView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? From c232d02f65160d13d68ee0be253bed1384a226bd Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Wed, 19 Jun 2024 04:00:50 +0800 Subject: [PATCH 11/16] fix tests --- .../HandlerTests/HandlerTestBasementOfT.iOS.cs | 2 ++ .../DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs index 7cd9ba8a8305..dca1d539da7c 100644 --- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs +++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs @@ -102,6 +102,8 @@ protected Visibility GetVisibility(IViewHandler viewHandler) protected bool GetUserInteractionEnabled(IViewHandler viewHandler) { var platformView = (UIView)viewHandler.PlatformView; + if (platformView is IUserInteractionEnabledManagingView maui) + return maui.UserInteractionEnabledOverride; return platformView.UserInteractionEnabled; } diff --git a/src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs index 956cc11f764b..7d04d7eebce0 100644 --- a/src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/ContentView/ContentViewTests.iOS.cs @@ -5,7 +5,6 @@ namespace Microsoft.Maui.DeviceTests.Handlers.ContentView { - [Category(TestCategory.ContentView)] public partial class ContentViewTests { [Fact, Category(TestCategory.FlowDirection)] From 8d97c27c7ea55d504819ee81e97b79f29cd3704b Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 20 Jun 2024 19:38:57 +0800 Subject: [PATCH 12/16] use the same interface --- .../Elements/Layout/LayoutTests.iOS.cs | 4 +-- .../IUserInteractionEnabledManagingView.cs | 6 ---- src/Core/src/Platform/iOS/MauiScrollView.cs | 28 ++++--------------- src/Core/src/Platform/iOS/MauiView.cs | 24 +++------------- src/Core/src/Platform/iOS/ViewExtensions.cs | 18 ++++++------ .../HandlerTestBasementOfT.iOS.cs | 4 +-- 6 files changed, 24 insertions(+), 60 deletions(-) delete mode 100644 src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs diff --git a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs index 9bda2f08deb8..aefe0f12bbae 100644 --- a/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs +++ b/src/Controls/tests/DeviceTests/Elements/Layout/LayoutTests.iOS.cs @@ -160,8 +160,8 @@ void ValidateInputTransparentOnPlatformView(IView view) var platformView = view.ToPlatform(MauiContext); bool value = platformView.UserInteractionEnabled; - if (platformView is IUserInteractionEnabledManagingView lv) - value = lv.UserInteractionEnabledOverride; + if (platformView is IInputTransparentManagingView lv) + value = !lv.InputTransparent; Assert.True(view.InputTransparent == !value, $"InputTransparent: {view.InputTransparent}. UserInteractionEnabled: {platformView.UserInteractionEnabled}"); diff --git a/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs b/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs deleted file mode 100644 index 54b540224b9f..000000000000 --- a/src/Core/src/Platform/iOS/IUserInteractionEnabledManagingView.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Microsoft.Maui.Platform; - -interface IUserInteractionEnabledManagingView -{ - bool UserInteractionEnabledOverride { get; } -} diff --git a/src/Core/src/Platform/iOS/MauiScrollView.cs b/src/Core/src/Platform/iOS/MauiScrollView.cs index 829087388aa8..226ae7bfb839 100644 --- a/src/Core/src/Platform/iOS/MauiScrollView.cs +++ b/src/Core/src/Platform/iOS/MauiScrollView.cs @@ -5,14 +5,14 @@ namespace Microsoft.Maui.Platform { - public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents, IUserInteractionEnabledManagingView + public class MauiScrollView : UIScrollView, IUIViewLifeCycleEvents, IInputTransparentManagingView { - bool _userInteractionEnabledOverride; - public MauiScrollView() { } + bool IInputTransparentManagingView.InputTransparent { get; set; } + // overriding this method so it does not automatically scroll large UITextFields // while the KeyboardAutoManagerScroll is scrolling. public override void ScrollRectToVisible(CGRect rect, bool animated) @@ -44,7 +44,7 @@ public override void MovedToWindow() return null; } - if (!_userInteractionEnabledOverride && Equals(result)) + if (((IInputTransparentManagingView)this).InputTransparent && Equals(result)) { // If user interaction is disabled (IOW, if the corresponding View is InputTransparent), // then we exclude the managing view itself from hit testing. But it's children are valid @@ -53,9 +53,9 @@ public override void MovedToWindow() return null; } - if (result is IUserInteractionEnabledManagingView v && !v.UserInteractionEnabledOverride) + if (result is IInputTransparentManagingView v && v.InputTransparent) { - // If the child is a managing view then we need to check the UserInteractionEnabledOverride + // If the child is a managing view then we need to check the InputTransparent // since managing view instances always have user interaction enabled. return null; @@ -63,21 +63,5 @@ public override void MovedToWindow() return result; } - - bool IUserInteractionEnabledManagingView.UserInteractionEnabledOverride => _userInteractionEnabledOverride; - - public override bool UserInteractionEnabled - { - get => base.UserInteractionEnabled; - set - { - // We leave the base UIE value true no matter what, so that hit testing will find children - // of the LayoutView. But we track the intended value so we can use it during hit testing - // to ignore the LayoutView itself, if necessary. - - base.UserInteractionEnabled = true; - _userInteractionEnabledOverride = value; - } - } } } diff --git a/src/Core/src/Platform/iOS/MauiView.cs b/src/Core/src/Platform/iOS/MauiView.cs index ff88e3d1e0f3..846638c77e67 100644 --- a/src/Core/src/Platform/iOS/MauiView.cs +++ b/src/Core/src/Platform/iOS/MauiView.cs @@ -7,7 +7,7 @@ namespace Microsoft.Maui.Platform { - public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IUIViewLifeCycleEvents, IUserInteractionEnabledManagingView + public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTreeElementProvidable, IUIViewLifeCycleEvents, IInputTransparentManagingView { static bool? _respondsToSafeArea; @@ -17,7 +17,7 @@ public abstract class MauiView : UIView, ICrossPlatformLayoutBacking, IVisualTre WeakReference? _reference; WeakReference? _crossPlatformLayoutReference; - bool _userInteractionEnabledOverride; + bool IInputTransparentManagingView.InputTransparent { get; set; } public IView? View { @@ -186,7 +186,7 @@ public override void MovedToWindow() return null; } - if (!_userInteractionEnabledOverride && Equals(result)) + if (((IInputTransparentManagingView)this).InputTransparent && Equals(result)) { // If user interaction is disabled (IOW, if the corresponding View is InputTransparent), // then we exclude the managing view itself from hit testing. But it's children are valid @@ -195,7 +195,7 @@ public override void MovedToWindow() return null; } - if (result is IUserInteractionEnabledManagingView v && !v.UserInteractionEnabledOverride) + if (result is IInputTransparentManagingView v && v.InputTransparent) { // If the child is a managing view then we need to check the UserInteractionEnabledOverride // since managing view instances always have user interaction enabled. @@ -205,21 +205,5 @@ public override void MovedToWindow() return result; } - - bool IUserInteractionEnabledManagingView.UserInteractionEnabledOverride => _userInteractionEnabledOverride; - - public override bool UserInteractionEnabled - { - get => base.UserInteractionEnabled; - set - { - // We leave the base UIE value true no matter what, so that hit testing will find children - // of the LayoutView. But we track the intended value so we can use it during hit testing - // to ignore the LayoutView itself, if necessary. - - base.UserInteractionEnabled = true; - _userInteractionEnabledOverride = value; - } - } } } diff --git a/src/Core/src/Platform/iOS/ViewExtensions.cs b/src/Core/src/Platform/iOS/ViewExtensions.cs index bec1da442bfc..d947adeede56 100644 --- a/src/Core/src/Platform/iOS/ViewExtensions.cs +++ b/src/Core/src/Platform/iOS/ViewExtensions.cs @@ -505,20 +505,22 @@ internal static Size LayoutToMeasuredSize(this IView view, double width, double public static void UpdateInputTransparent(this UIView platformView, IViewHandler handler, IView view) { if (view is ITextInput textInput) - { platformView.UpdateInputTransparent(textInput.IsReadOnly, view.InputTransparent); - return; - } - - platformView.UserInteractionEnabled = !view.InputTransparent; + else + platformView.UpdateInputTransparent(view.InputTransparent); } - public static void UpdateInputTransparent(this UIView platformView, bool isReadOnly, bool inputTransparent) + public static void UpdateInputTransparent(this UIView platformView, bool isReadOnly, bool inputTransparent) => + platformView.UpdateInputTransparent(isReadOnly || inputTransparent); + + internal static void UpdateInputTransparent(this UIView platformView, bool inputTransparent) { - platformView.UserInteractionEnabled = !(isReadOnly || inputTransparent); + if (platformView is IInputTransparentManagingView itmv) + itmv.InputTransparent = inputTransparent; + else + platformView.UserInteractionEnabled = !inputTransparent; } - internal static UIToolTipInteraction? GetToolTipInteraction(this UIView platformView) { UIToolTipInteraction? interaction = default; diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs index dca1d539da7c..5175a81c22d2 100644 --- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs +++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.iOS.cs @@ -102,8 +102,8 @@ protected Visibility GetVisibility(IViewHandler viewHandler) protected bool GetUserInteractionEnabled(IViewHandler viewHandler) { var platformView = (UIView)viewHandler.PlatformView; - if (platformView is IUserInteractionEnabledManagingView maui) - return maui.UserInteractionEnabledOverride; + if (platformView is IInputTransparentManagingView maui) + return !maui.InputTransparent; return platformView.UserInteractionEnabled; } From 05475f2a70ddd434c1f2ced2d88126382bbf2711 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 20 Jun 2024 20:00:33 +0800 Subject: [PATCH 13/16] not for windows yet --- .../Tests/Concepts/InputTransparencyGalleryTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs index 827546201408..86aab98127d8 100644 --- a/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Concepts/InputTransparencyGalleryTests.cs @@ -42,6 +42,8 @@ public void InputTransparencyWhenRootIsNotTransparentMatrix([Values] bool rootCa RunTest(key, clickable, passthru); } +#if !WINDOWS + [Test] [Combinatorial] public void ScrollViewInputTransparencySimpleMatrix([Values] bool rootTrans, [Values] bool rootCascade, [Values] bool trans) @@ -63,6 +65,8 @@ public void ContentViewInputTransparencySimpleMatrix([Values] bool rootTrans, [V RunTest(key, clickable, passthru); } +#endif + void RunTest(string test, bool? clickable = null, bool? passthru = null, bool androidIsBroken = true) { var remote = new EventViewContainerRemote(UITestContext, test); From f5282cfce8c1a5a654476acfa06fb51203e0ddc1 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Thu, 20 Jun 2024 20:03:06 +0800 Subject: [PATCH 14/16] remove --- src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt | 4 ---- .../src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt index c64aeb8debc8..418719b4a824 100644 --- a/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt @@ -165,9 +165,5 @@ override Microsoft.Maui.Handlers.BorderHandler.PlatformArrange(Microsoft.Maui.Gr *REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.get -> bool *REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.set -> void *REMOVED*override Microsoft.Maui.Platform.LayoutView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? -override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.get -> bool -override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.set -> void override Microsoft.Maui.Platform.MauiView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? -override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.get -> bool -override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.set -> void override Microsoft.Maui.Platform.MauiScrollView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? diff --git a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt index b433d76b27f4..de76d1791c44 100644 --- a/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt @@ -165,9 +165,5 @@ static Microsoft.Maui.Platform.UIEdgeInsetsExtensions.ToThickness(this UIKit.UIE *REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.get -> bool *REMOVED*override Microsoft.Maui.Platform.LayoutView.UserInteractionEnabled.set -> void *REMOVED*override Microsoft.Maui.Platform.LayoutView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? -override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.get -> bool -override Microsoft.Maui.Platform.MauiView.UserInteractionEnabled.set -> void override Microsoft.Maui.Platform.MauiView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? -override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.get -> bool -override Microsoft.Maui.Platform.MauiScrollView.UserInteractionEnabled.set -> void override Microsoft.Maui.Platform.MauiScrollView.HitTest(CoreGraphics.CGPoint point, UIKit.UIEvent? uievent) -> UIKit.UIView? From af8197f8b77c78ed30ece7ee6452f715af4a7fc4 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Mon, 24 Jun 2024 23:29:46 +0800 Subject: [PATCH 15/16] Update ScrollViewHandler.iOS.cs --- src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs index ec2605fffe8d..cb6e5200ef33 100644 --- a/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs +++ b/src/Core/src/Handlers/ScrollView/ScrollViewHandler.iOS.cs @@ -187,6 +187,10 @@ static void InsertContentView(UIScrollView platformScrollView, IScrollView scrol Tag = ContentPanelTag }; + // The content view should be invisible to the user since we are "secretly" + // using it to host the ScrollView's content instead of the ScrollView itself. + ((IInputTransparentManagingView)contentContainer).InputTransparent = true; + // This is where we normally would inject the cross-platform ScrollView's layout logic; instead, we're injecting the // methods from this handler so it can make some adjustments for things like Padding before the default logic is invoked contentContainer.CrossPlatformLayout = crossPlatformLayout; From ba07c08577d6251d199b99b473d41622bc8ed1d4 Mon Sep 17 00:00:00 2001 From: Matthew Leibowitz Date: Tue, 25 Jun 2024 00:52:29 +0800 Subject: [PATCH 16/16] Added some comments --- src/Core/src/ViewExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/src/ViewExtensions.cs b/src/Core/src/ViewExtensions.cs index 19bc9747096e..5fde0953aab7 100644 --- a/src/Core/src/ViewExtensions.cs +++ b/src/Core/src/ViewExtensions.cs @@ -56,6 +56,10 @@ internal static bool NeedsContainer(this IView? view, PlatformView? platformView #endif #if ANDROID + // This is only here for Android because almost all Android views will require + // a wrapper when the view is InputTransparent. This is because Android does not + // have a concept of "not hit testable" so we have to emulate it intercepting the + // the touch events with a parent layout. if (view?.InputTransparent == true && platformView is not IInputTransparentManagingView) return true; #endif