Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Microsoft.Maui.Controls;
using Microsoft.Maui;

namespace Maui.Controls.Sample
{
Expand Down Expand Up @@ -147,8 +148,52 @@ 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<ContentView>(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<ScrollView>(rt, rc, t, clickable, passthru);
}
}

void AddSimpleNesting<T>(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 => 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), () =>
{
Expand Down Expand Up @@ -178,16 +223,15 @@ 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);
.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<View> viewContainer, Button bottom, Button top)
{
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();
bottom.Clicked += (s, e) => viewContainer.ReportFailEvent();
top.Clicked += (s, e) => viewContainer.ReportSuccessEvent();
}
else if (!isPassThru)
{
Expand All @@ -196,20 +240,20 @@ void AddNesting(bool rootTrans, bool rootCascade, bool nestedTrans, bool nestedC
#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) => 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();
bottom.Clicked += (s, e) => viewContainer.ReportSuccessEvent();
top.Clicked += (s, e) => viewContainer.ReportFailEvent();
}
});
}

(ExpectedEventViewContainer<View> ViewContainer, T Additional) Add<T>(Test.InputTransparency test, Func<(View View, T Additional)> func) =>
Add(test.ToString(), func);
Expand Down
16 changes: 16 additions & 0 deletions src/Controls/samples/Controls.Sample.UITests/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 1 addition & 4 deletions src/Core/src/Handlers/Layout/LayoutHandler.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is no longer needed as we do it in the ViewHandler mapper.

}
}
}
8 changes: 5 additions & 3 deletions src/Core/src/Handlers/View/ViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public bool HasContainer
/// </summary>
public virtual bool NeedsContainer
{
get => VirtualView.NeedsContainer();
get => VirtualView.NeedsContainer(PlatformView);
}

/// <summary>
Expand Down Expand Up @@ -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);
}

/// <summary>
Expand Down Expand Up @@ -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

Expand Down
14 changes: 13 additions & 1 deletion src/Core/src/Platform/Android/ContentViewGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Platform/Android/LayoutViewGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 14 additions & 9 deletions src/Core/src/Platform/Android/MauiScrollView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
{
Expand Down Expand Up @@ -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.
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/Core/src/Platform/Android/WrapperView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Microsoft.Maui.Platform
{
public partial class WrapperView : PlatformWrapperView
public partial class WrapperView : PlatformWrapperView, IInputTransparentManagingView
{
const int MaximumRadius = 100;

Expand Down
7 changes: 7 additions & 0 deletions src/Core/src/Platform/IInputTransparentManagingView.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.Maui.Platform
{
internal interface IInputTransparentManagingView
{
bool InputTransparent { get; set; }
}
}
8 changes: 0 additions & 8 deletions src/Core/src/Platform/Tizen/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(this NView view, int index) where T : NView
{
return (T?)view.Children[index];
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.MauiMaterialButton.IconGravity.get -> int
override Microsoft.Maui.Platform.MauiMaterialButton.IconGravity.set -> void
override Microsoft.Maui.Platform.MauiScrollView.OnMeasure(int widthMeasureSpec, int heightMeasureSpec) -> void
Expand Down
13 changes: 8 additions & 5 deletions src/Core/src/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,30 @@ public static partial class ViewExtensions
await Screenshot.Default.CaptureAsync(window);
#endif

#if !TIZEN
internal static bool NeedsContainer(this IView? view)
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)
if (view?.InputTransparent == true && (platformView is null || platformView is not IInputTransparentManagingView))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line matches the change in the transparency logic - if we can handle it ourselves, we don't wrap.

return true;
#endif

#if ANDROID || IOS
if (view is IBorder border && border.Border != null)
return true;
#elif WINDOWS
#endif

#if WINDOWS || TIZEN
if (view is IBorderView border)
return border?.Shape != null || border?.Stroke != null;
#endif

return false;
}
#endif

}
}