Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -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
Comment on lines +196 to +201
Copy link
Copy Markdown
Member Author

@mattleibow mattleibow Sep 13, 2023

Choose a reason for hiding this comment

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

Android currently is semi-broken: #10252 but fixed in #13725

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;
}
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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";
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
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
Loading