Skip to content

Commit

Permalink
Add tests and fix Windows + iOS
Browse files Browse the repository at this point in the history
Windows and iOs correctly handled transparency with regards to children, but did not allow taps to pass through
  • Loading branch information
mattleibow committed Sep 21, 2023
1 parent 39b3fda commit 86befcf
Show file tree
Hide file tree
Showing 18 changed files with 489 additions and 24 deletions.
@@ -0,0 +1,239 @@
using System;
using Microsoft.Maui.Controls;

namespace Maui.Controls.Sample
{
internal class InputTransparencyGalleryPage : CoreGalleryBasePage
{
protected override void Build()
{
// Basic test with view defaults, should be clickable
Add(Test.InputTransparency.Default, new Button { Text = "Click Me!" })
.With(t => t.View.Clicked += (s, e) => t.ReportSuccessEvent());

// Test when InputTransparent is explicitly set to False, should be clickable
Add(Test.InputTransparency.IsFalse, new Button { Text = "Click Me!", InputTransparent = false })
.With(t => t.View.Clicked += (s, e) => t.ReportSuccessEvent());

// Test when InputTransparent is explicitly set to True, should NOT be clickable
// and we emulate this by putting another button underneath that should be clickable
Add(Test.InputTransparency.IsTrue, () =>
{
var bottom = new Button { Text = "Bottom Button" };
var top = new Button { Text = "Click Me!", InputTransparent = true };
var grid = new Grid { bottom, top };
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);
bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent();
top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent();
});

// Test when there is an InputTransparent layout over the button, should be clickable
Add(Test.InputTransparency.TransLayoutOverlay, () =>
{
var button = new Button { Text = "Click Me!" };
var grid = new Grid
{
new Grid { button },
new Grid
{
InputTransparent = true,
Background = Brush.Red,
Opacity = 0.5,
}
};
return (grid, new { Button = button });
})
.With(t =>
{
var v = t.ViewContainer.View;
var button = Annotate(t.Additional.Button, v);
button.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent();
});

// Test when there is an InputTransparent layout over the button, should NOT be clickable
// but the button IN the layout should be clickable because it is not cascading
Add(Test.InputTransparency.TransLayoutOverlayWithButton, () =>
{
var bottom = new Button { Text = "Bottom Button" };
var top = new Button { Text = "Click Me!" };
var grid = new Grid
{
new Grid { bottom },
new Grid
{
InputTransparent = true,
CascadeInputTransparent = false,
Background = Brush.Red,
Opacity = 0.5,
Children = { top },
}
};
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);
bottom.Clicked += (s, e) => t.ViewContainer.ReportFailEvent();
top.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent();
});

// Test when there is an InputTransparent layout over the button, should be clickable
Add(Test.InputTransparency.CascadeTransLayoutOverlay, () =>
{
var button = new Button { Text = "Click Me!" };
var grid = new Grid
{
new Grid { button },
new Grid
{
InputTransparent = true,
CascadeInputTransparent = true,
Background = Brush.Red,
Opacity = 0.5,
}
};
return (grid, new { Button = button });
})
.With(t =>
{
var v = t.ViewContainer.View;
var button = Annotate(t.Additional.Button, v);
button.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent();
});

// Test when there is an InputTransparent layout over the button, should be clickable
// and the button IN the layout should NOT be clickable because it is cascading
Add(Test.InputTransparency.CascadeTransLayoutOverlayWithButton, () =>
{
var bottom = new Button { Text = "Bottom Button" };
var top = new Button { Text = "Click Me!" };
var grid = new Grid
{
new Grid { bottom },
new Grid
{
InputTransparent = true,
CascadeInputTransparent = true,
Background = Brush.Red,
Opacity = 0.5,
Children = { top },
}
};
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);
bottom.Clicked += (s, e) => t.ViewContainer.ReportSuccessEvent();
top.Clicked += (s, e) => t.ViewContainer.ReportFailEvent();
});

// Tests for a nested layout (root grid, nested grid, button) with some variations to ensure
// that all combinations are correctly clickable
foreach (var state in Test.InputTransparencyMatrix.States)
{
var (rt, rc, nt, nc, t) = state.Key;
var (clickable, passthru) = state.Value;

AddNesting(rt, rc, nt, nc, t, clickable, passthru);
}
}

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), () =>
{
var bottom = new Button { Text = "Bottom Button" };
var top = new Button
{
InputTransparent = trans,
Text = "Click Me!"
};
var grid = new Grid
{
new Grid { bottom },
new Grid
{
InputTransparent = rootTrans,
CascadeInputTransparent = rootCascade,
Children =
{
new Grid
{
InputTransparent = nestedTrans,
CascadeInputTransparent = nestedCascade,
Children = { top }
}
},
}
};
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();
}
});

(ExpectedEventViewContainer<View> ViewContainer, T Additional) Add<T>(Test.InputTransparency test, Func<(View View, T Additional)> func) =>
Add(test.ToString(), func);

(ExpectedEventViewContainer<View> ViewContainer, T Additional) Add<T>(string test, Func<(View View, T Additional)> func)
{
var result = func();
var vc = new ExpectedEventViewContainer<View>(test, result.View);
Add(vc);
return (vc, result.Additional);
}

ExpectedEventViewContainer<Button> Add(Test.InputTransparency test, Button button) =>
Add(new ExpectedEventViewContainer<Button>(test, button));

static T Annotate<T>(T view, View desired)
where T : View
{
#if WINDOWS
// Windows does not have layouts in the automation tree
// and some of the tests have the layout as the root
view.AutomationId = desired.AutomationId;
#endif
return view;
}
}
}
Expand Up @@ -52,6 +52,7 @@ public override string ToString()
new GalleryPageFactory(() => new LabelCoreGalleryPage(), "Label Gallery"),
new GalleryPageFactory(() => new GestureRecognizerGallery(), "Gesture Recognizer Gallery"),
new GalleryPageFactory(() => new ScrollViewCoreGalleryPage(), "ScrollView Gallery"),
new GalleryPageFactory(() => new InputTransparencyGalleryPage(), "Input Transparency Gallery"),
};

public CorePageView(Page rootPage)
Expand Down
5 changes: 3 additions & 2 deletions src/Controls/samples/Controls.Sample.UITests/MauiProgram.cs
Expand Up @@ -61,8 +61,9 @@ protected override Window CreateWindow(IActivationState activationState)
window.Width = desktopWindowWidth;
window.Height = desktopWindowHeight;

int screenWidth = (int)Microsoft.Maui.Devices.DeviceDisplay.MainDisplayInfo.Width;
int screenHeight = (int)Microsoft.Maui.Devices.DeviceDisplay.MainDisplayInfo.Height;
var info = Microsoft.Maui.Devices.DeviceDisplay.MainDisplayInfo;
int screenWidth = (int)(info.Width / info.Density);
int screenHeight = (int)(info.Height / info.Density);

// Center the window on the screen, to ensure no part of it goes off screen in CI
window.X = (screenWidth - desktopWindowWidth) / 2;
Expand Down
61 changes: 60 additions & 1 deletion src/Controls/samples/Controls.Sample.UITests/Test.cs
@@ -1,4 +1,6 @@
namespace Maui.Controls.Sample
using System.Collections.Generic;

namespace Maui.Controls.Sample
{
public static class Test
{
Expand Down Expand Up @@ -707,5 +709,62 @@ public enum CarouselView
Position,
IsBounceEnabled
}

public enum InputTransparency
{
Default,
IsFalse,
IsTrue,
TransLayoutOverlay,
TransLayoutOverlayWithButton,
CascadeTransLayoutOverlay,
CascadeTransLayoutOverlayWithButton,
}

public static class InputTransparencyMatrix
{
// this is both for color diff and cols
const bool truee = true;

public static readonly IReadOnlyDictionary<(bool RT, bool RC, bool NT, bool NC, bool T), (bool Clickable, bool PassThru)> States =
new Dictionary<(bool, bool, bool, bool, bool), (bool, bool)>
{
[(truee, truee, truee, truee, truee)] = (false, truee),
[(truee, truee, truee, truee, false)] = (false, truee),
[(truee, truee, truee, false, truee)] = (false, truee),
[(truee, truee, truee, false, false)] = (false, truee),
[(truee, truee, false, truee, truee)] = (false, truee),
[(truee, truee, false, truee, false)] = (false, truee),
[(truee, truee, false, false, truee)] = (false, truee),
[(truee, truee, false, false, false)] = (false, truee),
[(truee, false, truee, truee, truee)] = (false, truee),
[(truee, false, truee, truee, false)] = (false, truee),
[(truee, false, truee, false, truee)] = (false, truee),
[(truee, false, truee, false, false)] = (truee, false),
[(truee, false, false, truee, truee)] = (false, false),
[(truee, false, false, truee, false)] = (truee, false),
[(truee, false, false, false, truee)] = (false, false),
[(truee, false, false, false, false)] = (truee, false),
[(false, truee, truee, truee, truee)] = (false, false),
[(false, truee, truee, truee, false)] = (false, false),
[(false, truee, truee, false, truee)] = (false, false),
[(false, truee, truee, false, false)] = (truee, false),
[(false, truee, false, truee, truee)] = (false, false),
[(false, truee, false, truee, false)] = (truee, false),
[(false, truee, false, false, truee)] = (false, false),
[(false, truee, false, false, false)] = (truee, false),
[(false, false, truee, truee, truee)] = (false, false),
[(false, false, truee, truee, false)] = (false, false),
[(false, false, truee, false, truee)] = (false, false),
[(false, false, truee, false, false)] = (truee, false),
[(false, false, false, truee, truee)] = (false, false),
[(false, false, false, truee, false)] = (truee, false),
[(false, false, false, false, truee)] = (false, false),
[(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";
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Globalization;
using Microsoft.Maui.Controls;

namespace Controls.Sample.Converters
{
public class NegativeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool v)
return !v;
else
return false;
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool v)
return !v;
else
return true;
}
}
}
Expand Up @@ -2,16 +2,12 @@
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Controls.Sample.Converters"
x:Class="Maui.Controls.Sample.Pages.InputTransparentPage">

<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
</Style>
<Style TargetType="Button">
<!-- <Setter Property="Padding" Value="14,10" /> -->
<!-- <Setter Property="WidthRequest" Value="200"/> -->
</Style>
<local:NegativeConverter x:Key="NegativeConverter" />
</ResourceDictionary>
</ContentPage.Resources>

Expand Down Expand Up @@ -65,11 +61,15 @@

<Grid Margin="10" HeightRequest="100" BackgroundColor="LightBlue">

<Button Text="Bottom Button" IsVisible="{Binding InputTransparent, Source={Reference testButton}}" Clicked="ClickSuccess" HorizontalOptions="Center" VerticalOptions="Center" />
<Button Text="Bottom Button" IsVisible="{Binding InputTransparent, Source={Reference testButton}, Converter={StaticResource NegativeConverter}}" Clicked="ClickFail" HorizontalOptions="Center" VerticalOptions="Center" />

<Grid x:Name="rootLayout">
<Grid x:Name="nestedLayout">
<Button x:Name="testButton" Text="Test Button" Clicked="ClickSuccess" HorizontalOptions="Center" VerticalOptions="Center" />
</Grid>
</Grid>

</Grid>

<Grid ColumnDefinitions="Auto,Auto,Auto,Auto,Auto,2" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="12" RowSpacing="6" Margin="10,0,10,0">
Expand Down

0 comments on commit 86befcf

Please sign in to comment.