From f9de03ce21e6f8813cdd97a8c5b92b1fd618d339 Mon Sep 17 00:00:00 2001 From: Steve Bilogan Date: Fri, 24 Mar 2023 18:53:45 -0400 Subject: [PATCH 1/3] feat: Add IndicatorPlacement to TabBar --- .../tabbar-indicator-placement-above.png | Bin 0 -> 1569 bytes .../tabbar-indicator-placement-below.png | Bin 0 -> 1976 bytes doc/controls/TabBarAndTabBarItem.md | 97 ++++++--- .../Uno.Toolkit.Samples.Shared/App.xaml | 1 - .../App.xaml.Navigation.cs | 1 + .../Tests/TabBarTests.cs | 186 +++++++++++++++++- .../Controls/TabBar/IndicatorPlacement.cs | 21 ++ .../Controls/TabBar/TabBar.Properties.cs | 15 ++ src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs | 19 +- .../Controls/TabBar/TabBar.xaml | 48 ++++- .../Styles/Controls/v2/TabBar.xaml | 46 ++++- 11 files changed, 394 insertions(+), 40 deletions(-) create mode 100644 doc/assets/tabbar-indicator-placement-above.png create mode 100644 doc/assets/tabbar-indicator-placement-below.png create mode 100644 src/Uno.Toolkit.UI/Controls/TabBar/IndicatorPlacement.cs diff --git a/doc/assets/tabbar-indicator-placement-above.png b/doc/assets/tabbar-indicator-placement-above.png new file mode 100644 index 0000000000000000000000000000000000000000..557c4cadb5186231f57f4fb34547c33b0749848f GIT binary patch literal 1569 zcmbW1do&XY9LJZUSh&q2&8xx;<2otU<8V>TY$LA-W7u|NQS(UG>#XG7tdXtOvYFf_ z$(usn&Ek$3am(9M!;x3Gc|Y#%ANS9D?>YVPdwkFNeShEoK6l+Q&dN$)B>(`R?1DJ& z0RZf=lZ6l9$nZ=x$3d!jv@9RQ_booSiaN3=)V0{|77z%87-Oeib3dv_U@?hU(mxb6q_eFb-r8n4oUd1mzs zxgI5ZO%Hq+y!8@Od+|`7DR_oos42*6JChe%Z7Y%L!BY)aeLi(FFV12TvCD|0y$7OoVnxH-ux)?H=8ycT~0*KE(f5`1c<;)wk@Rk{=rJpsAY`OC#VHk6B#?B#0s}2wP-Ff&l5}kJh?Zx_ zv-aTZWEcJ)elOCD#>^G*0vg2UsCpNd7wgVH_~O-x@oytI4p0)B?*J`NYNWnKd^xO@ z5Tpu06LOD14=MD5=zfnIWnh(g2p7 zw!&rBN({6Oo+MKiCtvO^um4_!8=Wv{LL{?xV!nJUGa{mByvqZ{Q)`i#*E{Y_;BHh4 zSdEpMMIeiTDh=HPq8nrmmLX~FuiZTDKTcz9$XA=z#5t{+#8d1QHiHPN_8BmqJja^8mD+BlhrlA>nFIcz zMxIY}nX|O9sb#PPK8Bk8Wc~Iy?o6lGBjt6AQZ3Z&n|hNysZ)5Z-&vq$x}@M^{DqM?J|KY^V(PDVtW-+)Fg7f)X5f)PgxbZx|XbKHa8@ zf*#NK76szs!ctg`Sgwh->m*-3Cps;JCxzR^D?}f0kcQigm`yGMz}cN!gVf z6GSsSLGvw~2kzM5AyIT73B}DRZ_?*Av@8S)mU?%!v=@yHtvZ1^jM+^xY&l8kmnFFi zZQU1tB8j50qD$D10prAYbAL0c7k1YvM~=Ze@(-rIpA`T0p_sB;USdcC6J-$mzwhZ+ bvQq#BANATork02@tOHywV9u911mF4#I1=t@ literal 0 HcmV?d00001 diff --git a/doc/assets/tabbar-indicator-placement-below.png b/doc/assets/tabbar-indicator-placement-below.png new file mode 100644 index 0000000000000000000000000000000000000000..0e0ec02d5db5878cb6e66896faa6215e946f2107 GIT binary patch literal 1976 zcmbVNX*3&%7LLv+p>(RMwLGdNDNXIFmPDgoD`KmLN?S_pttHgjs61q7OG}fnM-c0% zC2gpswdQG?vB#7|gfXZkA_zh$nGVruv_~3lR(P!n@@5+6(Y@!XyuFAZN)nb-2bSB=s`qqZ6(@Y(1 zE1>tXcfxR%E7MC21G!Loi^cx_KKj9>+so-!2~sgOJ1ztTvN+ONQ2~PRb)#xzdUX;| z$^S{{Z*&fEM+G6vO`;c3$oyIHMx#CiF36-lFvN@!sAFEs9V}-*njiT>y3rrl_#yum z)it{2B38r?ZmdT~d5js&xpPAThL_kAMiqtZ15_P!J8)CLnhbe9M{xQ-h5yvaUm#xR zi1Q{HlVH5Sk*=%$ZDVD&Wi?;Pf7@KuXD@d*)@s?lyjgR5ab>ob#f{q&5G)%w$k}#A zrSN@^B_VJb5ub(mVKix=&2VbqBx^MlO^qim;53m*cdKEG7J@Fxl|mo6XZ?-Dox`_j+b(M_XG-{^ zp_{!MV20rz5Tcz6*X2_wfD7PI;cs-T~$LD}`&cH!7?xS*9r{Eoi^ z6z6#}Hh?L=QD$fiW{e%PfK!L8906}mVA0`4btu=L-T{|&6c+E<(`e5OS6+dEydJG(X2ykZS0XQBj<-0h8$9d@@jo4RVZQn_-a;{4D#$c&=W4djVwi+8N_=JA|hz2jk`T0)! zU8$}^=Q}DzY{QNxO%v%vZvt4O-Oi1oQ`rr1qZI-4<#8rn_j`VL?8~%#g0qPNAK_+e zo&@6%SEuen?`=6xhZQ)XCdkx7UWelRtfWin4J%!EI(=n6}ClV5Ga7ZLO-UGg4juh_f)R@=P zfKD?>a=ugLULGd-VKjc--_0&8cR4XoB$P|W1+!ie96@#%^zv(s84JMx^#zh_7H7)h z3vA+Hth}X>nQ{D{VqvjrR#2VeIoSBA-vLf;UmHWz$a+^6^_*9P-;!twlir$Emoj** zJxz>71<0eDg|<0L`x=TS8+sET+=D*U+P&#eS57Ts$z)eo2kDbJAFgWFQ|7sAQdyrP#uAHjNy;r%T#;X zvNeYF^466%Z#9a*kf~bR9N^<~xyfflPo{ca2GB8*3n>MKze2T+QVKy3v1YA*_NkY* za2LH)q?DjlwT_=ZI%l9=OqeT%j*j!I4p{uX7mH+Nm9!9lVWvNAL{@l&gHvu(+1(Wp zLTV^7#S8s_asK_o*!5^AzdHo@Nh*mF#q{LuucAW{TYIt-g8`_>sPM)TEf0w3D^30F zLS&>{q~7Ip?V$CxHc|x52$mvhGB>U2xbWm)^@b$0g1fuw`g@(J#d#BMEGpq0-bg-I zc2MSv*V*7xP>|~sgcIX4--Q=z#tAbkoMTW^H9%s58dzX=bt<-77nF8B)FEIvLQz!u zi!&v`p*`!OWb`3+Ac;55=3$J7eI|)<7m8iF5`H&xO9e;7-bqZJnFWp>Yt?tvmcDc1 z?YJ7B_wwKOF-+OZp-CyHg?U-vZS8rhCu87I(wqzH%lQJ2ES$Vv6bwhcJ{`s=Z9t?X zgU%j@)|s+%t1uIv`5#oAIHr@a8pZ9(EWX9&hpmpCJJ-g2D^7GR&*t5`*W>jYTKn+h zc)xLPsc~KP63ks+n#2y2L$fGDnCRYl-b=_?$bgak{O45js7a@BW* zQX+X=XnYvk6s#g^FnUDdIpu?xPknAuX7KsGy}#{xgKqC8Hk5vGr)GT~mmVSHHF$n@ zm$SWP_CJ5eU%1$R3en#X$=Na~m}JeLk4R^g8bI6#zfe69+VsT#LOdzoR?BcA{mf2S S?>zXn0L0%NtZS`26aN5av&aAd literal 0 HcmV?d00001 diff --git a/doc/controls/TabBarAndTabBarItem.md b/doc/controls/TabBarAndTabBarItem.md index 33dc4cb9e..c6d1337c6 100644 --- a/doc/controls/TabBarAndTabBarItem.md +++ b/doc/controls/TabBarAndTabBarItem.md @@ -108,6 +108,7 @@ SelectionIndicatorContent|object|Gets or sets the content to be displayed as the SelectionIndicatorContentTemplate|DataTemplate|Gets or sets the data template that is used to display the content of the selection indicator SelectionIndicatorPresenterStyle|Style|Gets or sets the style to be applied for the content of the `TabBarSelectionIndicatorPresenter` SelectionIndicatorTransitionMode|IndicatorTransitionMode|Gets or sets the behavior of the selection indicator. The indicator can either slide or snap to the newly selected item. Defaults to `Snap` +SelectionIndicatorPlacement|IndicatorPlacement|Gets or sets the placement of the selection indicator. The indicator can either be displayed `Above` or `Below` the TabBarItem Content. Defaults to `Above` > Note: `TabBar` only supports the single selection mode. @@ -141,29 +142,29 @@ xmlns:utu="using:Uno.Toolkit.UI" ... - - - - - - - - - - - - - - - - - - - - + SelectedIndex="0"> + + + + + + + + + + + + + + + + + + + + ``` @@ -182,6 +183,56 @@ The selection indicator has two different transition modes: ![TabBar with slide selection](../assets/tabbar-selection-slide.gif) +### IndicatorPlacement + +By default, the selection indicator is rendered above, on "on top" of, the actual content in the TabBar. For example, the following XAML will result in the screenshot below: + +```xml + + + + + + + + + + + + +``` + +![](../assets/tabbar-indicator-placement-above.png) + +The `SelectionIndicatorPlacement` property can be used to render the selection indicator "below" the content so it does not obscure the `TabBarItem`: + +```xml + + + + + + + + + + + + +``` + +![](../assets/tabbar-indicator-placement-below.png) + + ### Further Customization If further customization is required, such as custom animations when sliding the indicator, there is a `SelectionIndicatorPresenterStyle` property on `TabBar` that can be set to customize the style of the internal `ContentPresenter` that is used to display the selection indicator. diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml index 35bdee902..87cd0362b 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml @@ -15,7 +15,6 @@ diff --git a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs index 05b35a567..ea9826709 100644 --- a/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs +++ b/samples/Uno.Toolkit.Samples/Uno.Toolkit.Samples.Shared/App.xaml.Navigation.cs @@ -23,6 +23,7 @@ using Microsoft.Extensions.Logging; using VisualTreeHelperEx = Uno.Toolkit.Samples.Helpers.VisualTreeHelperEx; using Uno.Toolkit.Samples.Content.NestedSamples; +using Uno.Toolkit.Samples.Content; namespace Uno.Toolkit.Samples { diff --git a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs index a40512873..172fca0db 100644 --- a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs +++ b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs @@ -10,17 +10,21 @@ using Uno.UI.RuntimeTests; using Uno.UI.Extensions; using Windows.Foundation; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.UI; +using Windows.Foundation.Metadata; #if IS_WINUI using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml; using Microsoft.UI; using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; #else using Windows.UI.Xaml.Controls; using Windows.UI.Xaml; -using Windows.UI; using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; #endif namespace Uno.Toolkit.RuntimeTests.Tests @@ -261,5 +265,185 @@ double GetMinCalculatedDimen() }; } } + + [TestMethod] + public async Task Verify_Indicator_Display_On_Selection() + { + if (!CanTakeScreenshot()) + { + Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; + } + + const int NumItems = 3; + var SUT = new TabBar + { + Background = new SolidColorBrush(Colors.Transparent), + SelectionIndicatorContentTemplate = XamlHelper.LoadXaml(@" + + + + "), + SelectionIndicatorPlacement = IndicatorPlacement.Above, + }; + + foreach (var item in Enumerable.Range(0, NumItems).Select(_ => CreateItem())) + { + SUT.Items.Add(item); + } + + await UnitTestUIContentHelperEx.SetContentAndWait(SUT); + + + + for (int i = 0; i < NumItems; i++) + { + SUT.SelectedIndex = i; + await UnitTestsUIContentHelper.WaitForIdle(); + + var selectedItem = SUT.ContainerFromItem(SUT.SelectedItem) as TabBarItem; + Assert.IsNotNull(selectedItem); + + var renderer = await TakeScreenshot(SUT); + var centerPoint = selectedItem!.TransformToVisual(SUT).TransformPoint(new Point(selectedItem.ActualWidth / 2, selectedItem.ActualHeight / 2)); + var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); + + AssertExpectedColor(Colors.Red, pixel); + + foreach (var nonSelected in SUT.Items.Cast().Where(x => !x.IsSelected)) + { + centerPoint = nonSelected.TransformToVisual(SUT).TransformPoint(new Point(nonSelected.ActualWidth / 2, nonSelected.ActualHeight / 2)); + pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); + AssertExpectedColor(Colors.Green, pixel); + } + } + + TabBarItem CreateItem() + { + return new TabBarItem + { + Content = new Border + { + Padding = new Thickness(20), + Background = new SolidColorBrush(Colors.Green), + } + }; + } + } + + [TestMethod] + [DataRow(true, DisplayName = "Verify Indicator Above")] + [DataRow(false, DisplayName = "Verify Indicator Below")] + public async Task Verify_Indicator_Placement(bool isAbove) + { + var item = new TabBarItem + { + Content = new Border + { + Padding = new Thickness(20), + Background = new SolidColorBrush(Colors.Green), + } + }; + + var SUT = new TabBar + { + Background = new SolidColorBrush(Colors.Transparent), + SelectionIndicatorContentTemplate = XamlHelper.LoadXaml(@" + + + + "), + SelectedIndex = 0, + SelectionIndicatorPlacement = isAbove ? IndicatorPlacement.Above : IndicatorPlacement.Below, + }; + SUT.Items.Add(item); + + await UnitTestUIContentHelperEx.SetContentAndWait(SUT); + + var belowPresenter = VisualTreeHelperEx + .GetFirstDescendant(SUT, x => x.Name == "BelowSelectionIndicatorPresenter"); + var abovePresenter = VisualTreeHelperEx + .GetFirstDescendant(SUT, x => x.Name == "AboveSelectionIndicatorPresenter"); + + + var renderer = await TakeScreenshot(SUT); + + var centerPoint = item.TransformToVisual(SUT).TransformPoint(new Point(item.ActualWidth / 2, item.ActualHeight / 2)); + var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); + + + if (isAbove) + { + Assert.AreEqual(Visibility.Collapsed, belowPresenter!.Visibility); + Assert.AreEqual(Visibility.Visible, abovePresenter!.Visibility); + + if (pixel is { }) + { + AssertExpectedColor(Colors.Red, pixel); + } + } + else + { + Assert.AreEqual(Visibility.Visible, belowPresenter!.Visibility); + Assert.AreEqual(Visibility.Collapsed, abovePresenter!.Visibility); + + if (pixel is { }) + { + AssertExpectedColor(Colors.Green, pixel); + } + } + } + + private static void AssertExpectedColor(Color expected, Color? actual) + { + Assert.AreEqual(expected.A, actual?.A); + Assert.AreEqual(expected.R, actual?.R); + Assert.AreEqual(expected.G, actual?.G); + Assert.AreEqual(expected.B, actual?.B); + } + + private static async Task GetColorAt(RenderTargetBitmap? bitmap, int x, int y) + { + if (bitmap is null) + { + return null; + } + + var pixelBuffer = await bitmap.GetPixelsAsync(); + var pixels = pixelBuffer.ToArray(); + + var offset = (y * bitmap.PixelWidth + x) * 4; + var a = pixels[offset + 3]; + var r = pixels[offset + 2]; + var g = pixels[offset + 1]; + var b = pixels[offset + 0]; + + return Color.FromArgb(a, r, g, b); + } + + + private static async Task TakeScreenshot(UIElement? element) + { + if (CanTakeScreenshot()) + { + var renderer = new RenderTargetBitmap(); + await renderer.RenderAsync(element); + return renderer; + } + else + { + return null; + } + } + + private static bool CanTakeScreenshot() + { + return ApiInformation.IsTypePresent( +#if IS_WINUI + "Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap" +#else + "Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap" +#endif + ); + } } } diff --git a/src/Uno.Toolkit.UI/Controls/TabBar/IndicatorPlacement.cs b/src/Uno.Toolkit.UI/Controls/TabBar/IndicatorPlacement.cs new file mode 100644 index 000000000..d992944ab --- /dev/null +++ b/src/Uno.Toolkit.UI/Controls/TabBar/IndicatorPlacement.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Uno.Toolkit.UI +{ + /// + /// Determines the placement of the selection indicator in a + /// + public enum IndicatorPlacement + { + /// + /// The selection indicator will be placed above the content of the + /// + Above, + /// + /// The selection indicator will be placed below, or "behind", the content of the + /// + Below, + } +} diff --git a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.Properties.cs b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.Properties.cs index da3ed911d..57fb6cdf2 100644 --- a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.Properties.cs +++ b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.Properties.cs @@ -132,6 +132,21 @@ public IndicatorTransitionMode SelectionIndicatorTransitionMode set => SetValue(SelectionIndicatorTransitionModeProperty, value); } + #endregion + #region DependencyProperty: SelectionIndicatorPlacement + + public static DependencyProperty SelectionIndicatorPlacementProperty { get; } = DependencyProperty.Register( + nameof(SelectionIndicatorPlacement), + typeof(IndicatorPlacement), + typeof(TabBar), + new PropertyMetadata(IndicatorPlacement.Above, OnPropertyChanged)); + + public IndicatorPlacement SelectionIndicatorPlacement + { + get => (IndicatorPlacement)GetValue(SelectionIndicatorPlacementProperty); + set => SetValue(SelectionIndicatorPlacementProperty, value); + } + #endregion private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) diff --git a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs index 8e083558a..1ed8f61bf 100644 --- a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs +++ b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.cs @@ -54,6 +54,7 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); UpdateOrientation(); + UpdateIndicatorPlacement(); } protected override bool IsItemItsOwnContainerOverride(object item) => item is TabBarItem; @@ -192,6 +193,22 @@ private void OnPropertyChanged(DependencyPropertyChangedEventArgs args) { UpdateOrientation(); } + else if (property == SelectionIndicatorPlacementProperty) + { + UpdateIndicatorPlacement(); + } + } + + private void UpdateIndicatorPlacement() + { + var state = SelectionIndicatorPlacement switch + { + IndicatorPlacement.Above => "Above", + IndicatorPlacement.Below => "Below", + _ => throw new ArgumentOutOfRangeException(nameof(SelectionIndicatorPlacement)) + }; + + VisualStateManager.GoToState(this, state, useTransitions: false); } private void UpdateOrientation() @@ -237,7 +254,7 @@ private void OnSelectedIndexChanged(DependencyPropertyChangedEventArgs? args) } TabBarItem? oldItem = null; - if (args?.OldValue is int oldIndex) + if (args?.OldValue is int oldIndex && oldIndex != -1) { oldItem = this.ContainerFromIndexSafe(oldIndex); } diff --git a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml index 395b0b650..42a146de5 100644 --- a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml +++ b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml @@ -75,28 +75,58 @@ - - + + + + - - + + + + + + + + + + + + + + + + + + + + - @@ -266,7 +296,11 @@ - + diff --git a/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml b/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml index b777ad29a..70a737726 100644 --- a/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml +++ b/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml @@ -74,28 +74,57 @@ - - + + + + - - + + + + + + + + + + + + + + + + + + + - @@ -816,7 +845,10 @@ - + From dcb103fd0312b93326c0e61339b4fd1fa1c09c56 Mon Sep 17 00:00:00 2001 From: Steve Bilogan Date: Mon, 17 Apr 2023 11:54:10 -0400 Subject: [PATCH 2/3] fix: pr comments --- .../Helpers/ImageAssertHelper.cs | 101 ++++++++++++++++++ .../Tests/TabBarTests.cs | 88 +++------------ .../Uno.Toolkit.RuntimeTests.props | 2 + .../Controls/TabBar/TabBar.xaml | 7 +- .../Styles/Controls/v2/TabBar.xaml | 7 +- 5 files changed, 121 insertions(+), 84 deletions(-) create mode 100644 src/Uno.Toolkit.RuntimeTests/Helpers/ImageAssertHelper.cs diff --git a/src/Uno.Toolkit.RuntimeTests/Helpers/ImageAssertHelper.cs b/src/Uno.Toolkit.RuntimeTests/Helpers/ImageAssertHelper.cs new file mode 100644 index 000000000..da690d9b9 --- /dev/null +++ b/src/Uno.Toolkit.RuntimeTests/Helpers/ImageAssertHelper.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FluentAssertions.Execution; +using System.Threading.Tasks; +using Windows.UI; +using Windows.Foundation.Metadata; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel.AppService; +using FluentAssertions; + +#if IS_WINUI +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml; +using Microsoft.UI; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Imaging; +#else +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Imaging; +#endif + +namespace Uno.Toolkit.RuntimeTests.Helpers +{ + internal static class ImageAssertHelper + { + /// + /// Asserts that the given contains the given color at the given and coordinates. + /// + /// The bitmap to check. + /// The expected color. + /// The x-coordinate of the pixel to check. + /// The y-coordinate of the pixel to check. + public static async Task AssertColorAt(this RenderTargetBitmap? bitmap, Color expected, int x, int y) + { + if (bitmap is null) + { + return; + } + + using var assertionScope = new AssertionScope(); + assertionScope.AddReportable("Expected Color", expected.ToString()); + assertionScope.AddReportable("Pixel Location", $"({x},{y})"); + + var pixelBuffer = await bitmap.GetPixelsAsync(); + var pixels = pixelBuffer.ToArray(); + + var offset = (y * bitmap.PixelWidth + x) * 4; + var a = pixels[offset + 3]; + var r = pixels[offset + 2]; + var g = pixels[offset + 1]; + var b = pixels[offset + 0]; + + var color = Color.FromArgb(a, r, g, b); + + AssertExpectedColor(expected, color); + } + + /// + /// Returns a screenshot of the given as a . + /// + /// The element to take a screenshot of. + public static async Task TakeScreenshot(this UIElement? element) + { + if (IsScreenshotSupported()) + { + var renderer = new RenderTargetBitmap(); + await renderer.RenderAsync(element); + return renderer; + } + else + { + return null; + } + } + + /// + /// Returns whether screenshots are supported on the current platform. + /// + public static bool IsScreenshotSupported() + { + return ApiInformation.IsTypePresent( +#if IS_WINUI + "Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap" +#else + "Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap" +#endif + ); + } + + private static void AssertExpectedColor(Color expected, Color? actual) + { + expected.Should().BeEquivalentTo(actual, config: d => d.ComparingByValue()); + } + } +} diff --git a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs index 172fca0db..b0d25a945 100644 --- a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs +++ b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs @@ -13,6 +13,7 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.UI; using Windows.Foundation.Metadata; +using FluentAssertions.Execution; #if IS_WINUI using Microsoft.UI.Xaml.Controls; @@ -269,7 +270,7 @@ double GetMinCalculatedDimen() [TestMethod] public async Task Verify_Indicator_Display_On_Selection() { - if (!CanTakeScreenshot()) + if (!ImageAssertHelper.IsScreenshotSupported()) { Assert.Inconclusive(); // System.NotImplementedException: RenderTargetBitmap is not supported on this platform.; } @@ -303,17 +304,15 @@ public async Task Verify_Indicator_Display_On_Selection() var selectedItem = SUT.ContainerFromItem(SUT.SelectedItem) as TabBarItem; Assert.IsNotNull(selectedItem); - var renderer = await TakeScreenshot(SUT); + var renderer = await SUT.TakeScreenshot(); var centerPoint = selectedItem!.TransformToVisual(SUT).TransformPoint(new Point(selectedItem.ActualWidth / 2, selectedItem.ActualHeight / 2)); - var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); - - AssertExpectedColor(Colors.Red, pixel); + + await renderer.AssertColorAt(Colors.Red, (int)centerPoint.X, (int)centerPoint.Y); foreach (var nonSelected in SUT.Items.Cast().Where(x => !x.IsSelected)) { centerPoint = nonSelected.TransformToVisual(SUT).TransformPoint(new Point(nonSelected.ActualWidth / 2, nonSelected.ActualHeight / 2)); - pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); - AssertExpectedColor(Colors.Green, pixel); + await renderer.AssertColorAt(Colors.Green, (int)centerPoint.X, (int)centerPoint.Y); } } @@ -331,9 +330,9 @@ TabBarItem CreateItem() } [TestMethod] - [DataRow(true, DisplayName = "Verify Indicator Above")] - [DataRow(false, DisplayName = "Verify Indicator Below")] - public async Task Verify_Indicator_Placement(bool isAbove) + [DataRow(IndicatorPlacement.Above, DisplayName = "Verify Indicator Above")] + [DataRow(IndicatorPlacement.Below, DisplayName = "Verify Indicator Below")] + public async Task Verify_Indicator_Placement(IndicatorPlacement placement) { var item = new TabBarItem { @@ -353,7 +352,7 @@ public async Task Verify_Indicator_Placement(bool isAbove) "), SelectedIndex = 0, - SelectionIndicatorPlacement = isAbove ? IndicatorPlacement.Above : IndicatorPlacement.Below, + SelectionIndicatorPlacement = placement, }; SUT.Items.Add(item); @@ -365,20 +364,18 @@ public async Task Verify_Indicator_Placement(bool isAbove) .GetFirstDescendant(SUT, x => x.Name == "AboveSelectionIndicatorPresenter"); - var renderer = await TakeScreenshot(SUT); - + var renderer = await SUT.TakeScreenshot(); var centerPoint = item.TransformToVisual(SUT).TransformPoint(new Point(item.ActualWidth / 2, item.ActualHeight / 2)); - var pixel = await GetColorAt(renderer, (int)centerPoint.X, (int)centerPoint.Y); - if (isAbove) + if (placement == IndicatorPlacement.Above) { Assert.AreEqual(Visibility.Collapsed, belowPresenter!.Visibility); Assert.AreEqual(Visibility.Visible, abovePresenter!.Visibility); - if (pixel is { }) + if (renderer is { }) { - AssertExpectedColor(Colors.Red, pixel); + await renderer.AssertColorAt(Colors.Red, (int)centerPoint.X, (int)centerPoint.Y); } } else @@ -386,64 +383,11 @@ public async Task Verify_Indicator_Placement(bool isAbove) Assert.AreEqual(Visibility.Visible, belowPresenter!.Visibility); Assert.AreEqual(Visibility.Collapsed, abovePresenter!.Visibility); - if (pixel is { }) + if (renderer is { }) { - AssertExpectedColor(Colors.Green, pixel); + await renderer.AssertColorAt(Colors.Green, (int)centerPoint.X, (int)centerPoint.Y); } } } - - private static void AssertExpectedColor(Color expected, Color? actual) - { - Assert.AreEqual(expected.A, actual?.A); - Assert.AreEqual(expected.R, actual?.R); - Assert.AreEqual(expected.G, actual?.G); - Assert.AreEqual(expected.B, actual?.B); - } - - private static async Task GetColorAt(RenderTargetBitmap? bitmap, int x, int y) - { - if (bitmap is null) - { - return null; - } - - var pixelBuffer = await bitmap.GetPixelsAsync(); - var pixels = pixelBuffer.ToArray(); - - var offset = (y * bitmap.PixelWidth + x) * 4; - var a = pixels[offset + 3]; - var r = pixels[offset + 2]; - var g = pixels[offset + 1]; - var b = pixels[offset + 0]; - - return Color.FromArgb(a, r, g, b); - } - - - private static async Task TakeScreenshot(UIElement? element) - { - if (CanTakeScreenshot()) - { - var renderer = new RenderTargetBitmap(); - await renderer.RenderAsync(element); - return renderer; - } - else - { - return null; - } - } - - private static bool CanTakeScreenshot() - { - return ApiInformation.IsTypePresent( -#if IS_WINUI - "Microsoft.UI.Xaml.Media.Imaging.RenderTargetBitmap" -#else - "Windows.UI.Xaml.Media.Imaging.RenderTargetBitmap" -#endif - ); - } } } diff --git a/src/Uno.Toolkit.RuntimeTests/Uno.Toolkit.RuntimeTests.props b/src/Uno.Toolkit.RuntimeTests/Uno.Toolkit.RuntimeTests.props index 39025901f..db179b552 100644 --- a/src/Uno.Toolkit.RuntimeTests/Uno.Toolkit.RuntimeTests.props +++ b/src/Uno.Toolkit.RuntimeTests/Uno.Toolkit.RuntimeTests.props @@ -35,11 +35,13 @@ + + diff --git a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml index 42a146de5..42b39e13a 100644 --- a/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml +++ b/src/Uno.Toolkit.UI/Controls/TabBar/TabBar.xaml @@ -91,12 +91,7 @@ - - - - - - + diff --git a/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml b/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml index 70a737726..da937e992 100644 --- a/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml +++ b/src/library/Uno.Toolkit.Material/Styles/Controls/v2/TabBar.xaml @@ -90,12 +90,7 @@ - - - - - - + From 4c978e5e95a71c118e072e7443c7a95c1d34e6bc Mon Sep 17 00:00:00 2001 From: Steve Bilogan Date: Mon, 17 Apr 2023 11:54:23 -0400 Subject: [PATCH 3/3] fix: pr comments --- src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs index b0d25a945..14377b1f1 100644 --- a/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs +++ b/src/Uno.Toolkit.RuntimeTests/Tests/TabBarTests.cs @@ -13,7 +13,6 @@ using System.Runtime.InteropServices.WindowsRuntime; using Windows.UI; using Windows.Foundation.Metadata; -using FluentAssertions.Execution; #if IS_WINUI using Microsoft.UI.Xaml.Controls;